test_smilKeyTimes.xhtml (11195B)
1 <html xmlns="http://www.w3.org/1999/xhtml"> 2 <head> 3 <title>Test for SMIL keyTimes</title> 4 <script src="/tests/SimpleTest/SimpleTest.js"></script> 5 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 6 </head> 7 <body> 8 <a target="_blank" 9 href="https://bugzilla.mozilla.org/show_bug.cgi?id=557885">Mozilla Bug 10 557885</a> 11 <p id="display"></p> 12 <div id="content"> 13 <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="120px" height="120px"> 14 <circle cx="-100" cy="20" r="15" fill="blue" id="circle"/> 15 </svg> 16 </div> 17 <pre id="test"> 18 <script class="testbody" type="text/javascript"> 19 <![CDATA[ 20 /** Test for SMIL keyTimes */ 21 22 var gSvg = document.getElementById("svg"); 23 SimpleTest.waitForExplicitFinish(); 24 25 function main() 26 { 27 gSvg.pauseAnimations(); 28 29 var testCases = Array(); 30 31 // Simple case 32 testCases.push({ 33 'attr' : { 'values': '0; 50; 100', 34 'keyTimes': '0; .8; 1' }, 35 'times': [ [ 4, 25 ], 36 [ 8, 50 ], 37 [ 9, 75 ], 38 [ 10, 100 ] ] 39 }); 40 41 // Parsing tests 42 testCases.push(parseOk(' 0 ; .8;1 ')); // extra whitespace 43 testCases.push(parseNotOk(';0; .8; 1')); // leading semi-colon 44 testCases.push(parseNotOk('; .8; 1')); // leading semi-colon 45 testCases.push(parseOk('0; .8; 1;')); // trailing semi-colon 46 testCases.push(parseNotOk('')); // empty string 47 testCases.push(parseNotOk(' ')); // empty string 48 testCases.push(parseNotOk('0; .8')); // too few values 49 testCases.push(parseNotOk('0; .8; .9; 1')); // too many values 50 testCases.push(parseNotOk('0; 1; .8')); // non-increasing 51 testCases.push(parseNotOk('0; .8; .9')); // final value non-1 with 52 // calcMode=linear 53 testCases.push(parseOk('0; .8; .9', { 'calcMode': 'discrete' })); 54 testCases.push(parseNotOk('0.01; .8; 1')); // first value not 0 55 testCases.push(parseNotOk('0.01; .8; 1', { 'calcMode': 'discrete' })); 56 // first value not 0 57 testCases.push(parseNotOk('0; .8; 1.1')); // out of range 58 testCases.push(parseNotOk('-0.1; .8; 1')); // out of range 59 60 61 // 2 values 62 testCases.push({ 63 'attr' : { 'values': '0; 50', 64 'keyTimes': '0; 1' }, 65 'times': [ [ 6, 30 ] ] 66 }); 67 68 // 1 value 69 testCases.push({ 70 'attr' : { 'values': '50', 71 'keyTimes': ' 0' }, 72 'times': [ [ 7, 50 ] ] 73 }); 74 75 // 1 bad value 76 testCases.push({ 77 'attr' : { 'values': '50', 78 'keyTimes': '0.1' }, 79 'times': [ [ 0, -100 ] ] 80 }); 81 82 // 1 value, calcMode=discrete 83 testCases.push({ 84 'attr' : { 'values': '50', 85 'calcMode': 'discrete', 86 'keyTimes': ' 0' }, 87 'times': [ [ 7, 50 ] ] 88 }); 89 90 // 1 bad value, calcMode=discrete 91 testCases.push({ 92 'attr' : { 'values': '50', 93 'calcMode': 'discrete', 94 'keyTimes': '0.1' }, 95 'times': [ [ 0, -100 ] ] 96 }); 97 98 // from-to 99 testCases.push({ 100 'attr' : { 'from': '10', 101 'to': '20', 102 'keyTimes': '0.0; 1.0' }, 103 'times': [ [ 3.5, 13.5 ] ] 104 }); 105 106 // from-to calcMode=discrete 107 testCases.push({ 108 'attr' : { 'from': '10', 109 'to': '20', 110 'calcMode': 'discrete', 111 'keyTimes': '0.0; 0.7' }, 112 'times': [ [ 0, 10 ], 113 [ 6.9, 10 ], 114 [ 7.0, 20 ], 115 [ 10.0, 20 ], 116 [ 11.0, 20 ] ] 117 }); 118 119 // from-to calcMode=discrete one keyTime only 120 testCases.push({ 121 'attr' : { 'values': '20', 122 'calcMode': 'discrete', 123 'keyTimes': '0' }, 124 'times': [ [ 0, 20 ], 125 [ 6.9, 20 ], 126 [ 7.0, 20 ], 127 [ 10.0, 20 ], 128 [ 11.0, 20 ] ] 129 }); 130 131 // from-to calcMode=discrete one keyTime, mismatches no. values 132 testCases.push({ 133 'attr' : { 'values': '10; 20', 134 'calcMode': 'discrete', 135 'keyTimes': '0' }, 136 'times': [ [ 0, -100 ] ] 137 }); 138 139 // to 140 testCases.push({ 141 'attr' : { 'to': '100', 142 'keyTimes': '0.0; 1.0' }, 143 'times': [ [ 0, -100 ], 144 [ 7, 40 ] ] 145 }); 146 147 // to -- bad number of keyTimes (too many) 148 testCases.push({ 149 'attr' : { 'to': '100', 150 'keyTimes': '0.0; 0.5; 1.0' }, 151 'times': [ [ 2, -100 ] ] 152 }); 153 154 // unfrozen to calcMode=discrete two keyTimes 155 testCases.push({ 156 'attr' : { 'to': '100', 157 'calcMode': 'discrete', 158 'keyTimes': '0.0; 1.0', 159 'fill': 'remove' }, 160 'times': [ [ 0, -100 ], 161 [ 7, -100 ], 162 [ 10, -100 ], 163 [ 12, -100 ]] 164 }); 165 166 // frozen to calcMode=discrete two keyTimes 167 testCases.push({ 168 'attr' : { 'to': '100', 169 'calcMode': 'discrete', 170 'keyTimes': '0.0; 1.0' }, 171 'times': [ [ 0, -100 ], 172 [ 7, -100 ], 173 [ 10, 100 ], 174 [ 12, 100 ] ] 175 }); 176 177 // to calcMode=discrete -- bad number of keyTimes (one, expecting two) 178 testCases.push({ 179 'attr' : { 'to': '100', 180 'calcMode': 'discrete', 181 'keyTimes': '0' }, 182 'times': [ [ 0, -100 ], 183 [ 7, -100 ] ] 184 }); 185 186 // values calcMode=discrete 187 testCases.push({ 188 'attr' : { 'values': '0; 10; 20; 30', 189 'calcMode': 'discrete', 190 'keyTimes': '0;.2;.4;.6' }, 191 'times': [ [ 0, 0 ], 192 [ 1.9, 0 ], 193 [ 2, 10 ], 194 [ 3.9, 10 ], 195 [ 4.0, 20 ], 196 [ 5.9, 20 ], 197 [ 6.0, 30 ], 198 [ 9.9, 30 ], 199 [ 10.0, 30 ] ] 200 }); 201 202 // The following two accumulate tests are from SMIL 3.0 203 // (Note that this behaviour differs from that defined for SVG Tiny 1.2 which 204 // specifically excludes the last value: "Note that in the case of discrete 205 // animation, the frozen value that is used is the value of the animation just 206 // before the end of the active duration.") 207 // accumulate=none 208 testCases.push({ 209 'attr' : { 'values': '0; 10; 20', 210 'calcMode': 'discrete', 211 'keyTimes': '0;.5;1', 212 'fill': 'freeze', 213 'repeatCount': '2', 214 'accumulate': 'none' }, 215 'times': [ [ 0, 0 ], 216 [ 5, 10 ], 217 [ 10, 0 ], 218 [ 15, 10 ], 219 [ 20, 20 ], 220 [ 25, 20 ] ] 221 }); 222 223 // accumulate=sum 224 testCases.push({ 225 'attr' : { 'values': '0; 10; 20', 226 'calcMode': 'discrete', 227 'keyTimes': '0;.5;1', 228 'fill': 'freeze', 229 'repeatCount': '2', 230 'accumulate': 'sum' }, 231 'times': [ [ 0, 0 ], 232 [ 5, 10 ], 233 [ 10, 20 ], 234 [ 15, 30 ], 235 [ 20, 40 ], 236 [ 25, 40 ] ] 237 }); 238 239 // If the interpolation mode is paced, the keyTimes attribute is ignored. 240 testCases.push({ 241 'attr' : { 'values': '0; 10; 20', 242 'calcMode': 'paced', 243 'keyTimes': '0;.2;1' }, 244 'times': [ [ 0, 0 ], 245 [ 2, 4 ], 246 [ 5, 10 ] ] 247 }); 248 249 // SMIL 3 has: 250 // If the simple duration is indefinite and the interpolation mode is 251 // linear or spline, any keyTimes specification will be ignored. 252 // However, since keyTimes represent "a proportional offset into the simple 253 // duration of the animation element" surely discrete animation too cannot use 254 // keyTimes when the simple duration is indefinite. Hence SVGT 1.2 is surely 255 // more correct when it has: 256 // If the simple duration is indefinite, any 'keyTimes' specification will 257 // be ignored. 258 // (linear) 259 testCases.push({ 260 'attr' : { 'values': '0; 10; 20', 261 'dur': 'indefinite', 262 'keyTimes': '0;.2;1' }, 263 'times': [ [ 0, 0 ], 264 [ 5, 0 ] ] 265 }); 266 // (spline) 267 testCases.push({ 268 'attr' : { 'values': '0; 10; 20', 269 'dur': 'indefinite', 270 'calcMode': 'spline', 271 'keyTimes': '0;.2;1', 272 'keySplines': '0 0 1 1; 0 0 1 1' }, 273 'times': [ [ 0, 0 ], 274 [ 5, 0 ] ] 275 }); 276 // (discrete) 277 testCases.push({ 278 'attr' : { 'values': '0; 10; 20', 279 'dur': 'indefinite', 280 'calcMode': 'discrete', 281 'keyTimes': '0;.2;1' }, 282 'times': [ [ 0, 0 ], 283 [ 5, 0 ] ] 284 }); 285 286 for (var i = 0; i < testCases.length; i++) { 287 gSvg.setCurrentTime(0); 288 var test = testCases[i]; 289 290 // Create animation elements 291 var anim = createAnim(test.attr); 292 293 // Run samples 294 for (var j = 0; j < test.times.length; j++) { 295 var times = test.times[j]; 296 gSvg.setCurrentTime(times[0]); 297 checkSample(anim, times[1], times[0], i); 298 } 299 300 anim.remove(); 301 } 302 303 // fallback to discrete for non-additive animation 304 var attr = { 'values': 'butt; round; square', 305 'attributeName': 'stroke-linecap', 306 'calcMode': 'linear', 307 'keyTimes': '0;.2;1', 308 'fill': 'remove' }; 309 var anim = createAnim(attr); 310 var samples = [ [ 0, 'butt' ], 311 [ 1.9, 'butt' ], 312 [ 2.0, 'round' ], 313 [ 9.9, 'round' ], 314 [ 10, 'butt' ] // fill=remove so we'll never set it to square 315 ]; 316 for (var i = 0; i < samples.length; i++) { 317 var sample = samples[i]; 318 gSvg.setCurrentTime(sample[0]); 319 checkLineCapSample(anim, sample[1], sample[0], 320 "[non-interpolatable fallback]"); 321 } 322 anim.remove(); 323 324 SimpleTest.finish(); 325 } 326 327 function parseOk(str, extra) 328 { 329 var attr = { 'values': '0; 50; 100', 330 'keyTimes': str }; 331 if (typeof(extra) == "object") { 332 for (name in extra) { 333 attr[name] = extra[name]; 334 } 335 } 336 return { 337 'attr' : attr, 338 'times': [ [ 0, 0 ] ] 339 }; 340 } 341 342 function parseNotOk(str, extra) 343 { 344 var result = parseOk(str, extra); 345 result.times = [ [ 0, -100 ] ]; 346 return result; 347 } 348 349 function createAnim(attr) 350 { 351 const svgns = "http://www.w3.org/2000/svg"; 352 var anim = document.createElementNS(svgns, 'animate'); 353 anim.setAttribute('attributeName','cx'); 354 anim.setAttribute('dur','10s'); 355 anim.setAttribute('begin','0s'); 356 anim.setAttribute('fill','freeze'); 357 for (name in attr) { 358 anim.setAttribute(name, attr[name]); 359 } 360 return document.getElementById('circle').appendChild(anim); 361 } 362 363 function checkSample(anim, expectedValue, sampleTime, caseNum) 364 { 365 var msg = "Test case " + caseNum + 366 " (keyTimes: '" + anim.getAttribute('keyTimes') + "'" + 367 " calcMode: " + anim.getAttribute('calcMode') + "), " + 368 "t=" + sampleTime + 369 ": Unexpected sample value:"; 370 is(anim.targetElement.cx.animVal.value, expectedValue, msg); 371 } 372 373 function checkLineCapSample(anim, expectedValue, sampleTime, caseDescr) 374 { 375 var msg = "Test case " + caseDescr + 376 " (keyTimes: '" + anim.getAttribute('keyTimes') + "'" + 377 " calcMode: " + anim.getAttribute('calcMode') + "), " + 378 "t=" + sampleTime + 379 ": Unexpected sample value:"; 380 var actualValue = 381 window.getComputedStyle(anim.targetElement). 382 getPropertyValue('stroke-linecap'); 383 is(actualValue, expectedValue, msg); 384 } 385 386 window.addEventListener("load", main); 387 ]]> 388 </script> 389 </pre> 390 </body> 391 </html>