|
1 <html xmlns="http://www.w3.org/1999/xhtml"> |
|
2 <head> |
|
3 <title>Test for SMIL keyTimes</title> |
|
4 <script type="text/javascript" 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" style="display: none"> |
|
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.parentNode.removeChild(anim); |
|
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.parentNode.removeChild(anim); |
|
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, null). |
|
382 getPropertyValue('stroke-linecap'); |
|
383 is(actualValue, expectedValue, msg); |
|
384 } |
|
385 |
|
386 window.addEventListener("load", main, false); |
|
387 ]]> |
|
388 </script> |
|
389 </pre> |
|
390 </body> |
|
391 </html> |