OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 /* Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 * Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 * found in the LICENSE file. |
| 4 * |
| 5 * Exported function: |
| 6 * - assertAttributeInterpolation({property, from, to}, [{at: fraction, is: val
ue}]) |
| 7 * Constructs a test case for each fraction that asserts the expected val
ue |
| 8 * equals the value produced by interpolation between from and to using |
| 9 * SMIL and Web Animations. |
| 10 */ |
| 11 'use strict'; |
| 12 (() => { |
| 13 var interpolationTests = []; |
4 | 14 |
5 'use strict'; | 15 function createElement(tagName, container) { |
6 (function() { | 16 var element = document.createElement(tagName); |
7 var testCount = 0; | 17 if (container) { |
8 var animationEventCount = 0; | 18 container.appendChild(element); |
9 | |
10 var durationSeconds = 0.001; | |
11 var iterationCount = 0.5; | |
12 var delaySeconds = 0; | |
13 var fragment = document.createDocumentFragment(); | |
14 var fragmentAttachedListeners = []; | |
15 var style = document.createElement('style'); | |
16 fragment.appendChild(style); | |
17 | |
18 var smilTestsDiv = document.createElement('div'); | |
19 smilTestsDiv.id = 'smil-tests'; | |
20 smilTestsDiv.textContent = 'SVG SMIL:'; | |
21 fragment.appendChild(smilTestsDiv); | |
22 | |
23 var waTestsDiv = document.createElement('div'); | |
24 waTestsDiv.id = 'web-animations-tests'; | |
25 waTestsDiv.textContent = 'Web Animations API:'; | |
26 fragment.appendChild(waTestsDiv); | |
27 | |
28 var updateScheduled = false; | |
29 function maybeScheduleUpdate() { | |
30 if (updateScheduled) { | |
31 return; | |
32 } | 19 } |
33 updateScheduled = true; | 20 return element; |
34 setTimeout(function() { | |
35 updateScheduled = false; | |
36 document.body.appendChild(fragment); | |
37 requestAnimationFrame(function () { | |
38 fragmentAttachedListeners.forEach(function(listener) {listener();}); | |
39 }); | |
40 }, 0); | |
41 } | |
42 | |
43 function smilResults() { | |
44 var result = '\nSVG SMIL:\n'; | |
45 var targets = document.querySelectorAll('.target.smil'); | |
46 for (var i = 0; i < targets.length; i++) { | |
47 result += targets[i].getResultString() + '\n'; | |
48 } | |
49 return result; | |
50 } | |
51 | |
52 function waResults() { | |
53 var result = '\nWeb Animations API:\n'; | |
54 var targets = document.querySelectorAll('.target.web-animations'); | |
55 for (var i = 0; i < targets.length; i++) { | |
56 result += targets[i].getResultString() + '\n'; | |
57 } | |
58 return result; | |
59 } | |
60 | |
61 function dumpResults() { | |
62 var results = document.createElement('pre'); | |
63 results.id = 'results'; | |
64 results.textContent = smilResults() + waResults(); | |
65 document.body.appendChild(results); | |
66 } | 21 } |
67 | 22 |
68 // Constructs a timing function which produces 'y' at x = 0.5 | 23 // Constructs a timing function which produces 'y' at x = 0.5 |
69 function createEasing(y) { | 24 function createEasing(y) { |
70 // FIXME: if 'y' is > 0 and < 1 use a linear timing function and allow | 25 // FIXME: if 'y' is > 0 and < 1 use a linear timing function and allow |
71 // 'x' to vary. Use a bezier only for values < 0 or > 1. | 26 // 'x' to vary. Use a bezier only for values < 0 or > 1. |
72 if (y == 0) { | 27 if (y == 0) { |
73 return 'steps(1, end)'; | 28 return 'steps(1, end)'; |
74 } | 29 } |
75 if (y == 1) { | 30 if (y == 1) { |
76 return 'steps(1, start)'; | 31 return 'steps(1, start)'; |
77 } | 32 } |
78 if (y == 0.5) { | 33 if (y == 0.5) { |
79 return 'steps(2, end)'; | 34 return 'steps(2, end)'; |
80 } | 35 } |
81 // Approximate using a bezier. | 36 // Approximate using a bezier. |
82 var b = (8 * y - 1) / 6; | 37 var b = (8 * y - 1) / 6; |
83 return 'cubic-bezier(0, ' + b + ', 1, ' + b + ')'; | 38 return 'cubic-bezier(0, ' + b + ', 1, ' + b + ')'; |
84 } | 39 } |
85 | 40 |
86 function createTestContainer(description, className) { | 41 function assertAttributeInterpolation(params, expectations) { |
87 var testContainer = document.createElement('div'); | 42 interpolationTests.push({params, expectations}); |
88 testContainer.setAttribute('description', description); | |
89 testContainer.classList.add('test'); | |
90 if (className) { | |
91 testContainer.classList.add(className); | |
92 } | |
93 return testContainer; | |
94 } | 43 } |
95 | 44 |
96 function convertPropertyToCamelCase(property) { | |
97 return property.replace(/^-/, '').replace(/-\w/g, function(m) {return m[1].t
oUpperCase();}); | |
98 } | |
99 | |
100 function describeSMILTest(params) { | |
101 return '<animate /> ' + convertPropertyToCamelCase(params.property) + ': fro
m [' + params.from + '] to [' + params.to + ']'; | |
102 } | |
103 | |
104 function describeWATest(params) { | |
105 return 'element.animate() ' + convertPropertyToCamelCase(params.property) +
': from [' + params.from + '] to [' + params.to + ']'; | |
106 } | |
107 | |
108 var nextTestId = 0; | |
109 function assertAttributeInterpolation(params, expectations) { | |
110 var testId = 'test-' + ++nextTestId; | |
111 var nextCaseId = 0; | |
112 | |
113 var smilTestContainer = createTestContainer(describeSMILTest(params), testId
); | |
114 smilTestsDiv.appendChild(smilTestContainer); | |
115 expectations.forEach(function(expectation) { | |
116 if (expectation.at < 0 || expectation.at > 1) | |
117 return; | |
118 smilTestContainer.appendChild(makeInterpolationTest( | |
119 'smil', expectation.at, testId, 'case-' + ++nextCaseId, params, expect
ation.is)); | |
120 }); | |
121 | |
122 var waTestContainer = createTestContainer(describeWATest(params), testId); | |
123 waTestsDiv.appendChild(waTestContainer); | |
124 expectations.forEach(function(expectation) { | |
125 waTestContainer.appendChild(makeInterpolationTest( | |
126 'web-animations', expectation.at, testId, 'case-' + ++nextCaseId, para
ms, expectation.is)); | |
127 }); | |
128 | |
129 maybeScheduleUpdate(); | |
130 } | |
131 | |
132 | |
133 function roundNumbers(value) { | 45 function roundNumbers(value) { |
134 return value. | 46 return value. |
135 // Round numbers to two decimal places. | 47 // Round numbers to two decimal places. |
136 replace(/-?\d*\.\d+(e-?\d+)?/g, function(n) { | 48 replace(/-?\d*\.\d+(e-?\d+)?/g, function(n) { |
137 return (parseFloat(n).toFixed(2)). | 49 return (parseFloat(n).toFixed(2)). |
138 replace(/\.\d+/, function(m) { | 50 replace(/\.\d+/, function(m) { |
139 return m.replace(/0+$/, ''); | 51 return m.replace(/0+$/, ''); |
140 }). | 52 }). |
141 replace(/\.$/, ''). | 53 replace(/\.$/, ''). |
142 replace(/^-0$/, '0'); | 54 replace(/^-0$/, '0'); |
143 }); | 55 }); |
144 } | 56 } |
145 | 57 |
146 function normalizeValue(value) { | 58 function normalizeValue(value) { |
147 return roundNumbers(value). | 59 return roundNumbers(value). |
148 // Place whitespace between tokens. | 60 // Place whitespace between tokens. |
149 replace(/([\w\d.]+|[^\s])/g, '$1 '). | 61 replace(/([\w\d.]+|[^\s])/g, '$1 '). |
150 replace(/\s+/g, ' '); | 62 replace(/\s+/g, ' '); |
151 } | 63 } |
152 | 64 |
153 function createTargetContainer(id) { | 65 function createTarget(container) { |
154 var targetContainer = document.createElement('div'); | 66 var targetContainer = createElement('div'); |
155 var template = document.querySelector('#target-template'); | 67 var template = document.querySelector('#target-template'); |
156 if (template) { | 68 if (template) { |
157 targetContainer.appendChild(template.content.cloneNode(true)); | 69 targetContainer.appendChild(template.content.cloneNode(true)); |
158 // Remove whitespace text nodes at start / end. | 70 // Remove whitespace text nodes at start / end. |
159 while (targetContainer.firstChild.nodeType != Node.ELEMENT_NODE && !/\S/.t
est(targetContainer.firstChild.nodeValue)) { | 71 while (targetContainer.firstChild.nodeType != Node.ELEMENT_NODE && !/\S/.t
est(targetContainer.firstChild.nodeValue)) { |
160 targetContainer.removeChild(targetContainer.firstChild); | 72 targetContainer.removeChild(targetContainer.firstChild); |
161 } | 73 } |
162 while (targetContainer.lastChild.nodeType != Node.ELEMENT_NODE && !/\S/.te
st(targetContainer.lastChild.nodeValue)) { | 74 while (targetContainer.lastChild.nodeType != Node.ELEMENT_NODE && !/\S/.te
st(targetContainer.lastChild.nodeValue)) { |
163 targetContainer.removeChild(targetContainer.lastChild); | 75 targetContainer.removeChild(targetContainer.lastChild); |
164 } | 76 } |
165 // If the template contains just one element, use that rather than a wrapp
er div. | 77 // If the template contains just one element, use that rather than a wrapp
er div. |
166 if (targetContainer.children.length == 1 && targetContainer.childNodes.len
gth == 1) { | 78 if (targetContainer.children.length == 1 && targetContainer.childNodes.len
gth == 1) { |
167 targetContainer = targetContainer.firstChild; | 79 targetContainer = targetContainer.firstChild; |
168 targetContainer.remove(); | |
169 } | 80 } |
| 81 container.appendChild(targetContainer); |
170 } | 82 } |
171 var target = targetContainer.querySelector('.target') || targetContainer; | 83 var target = targetContainer.querySelector('.target') || targetContainer; |
172 target.classList.add('target'); | 84 target.container = targetContainer; |
173 target.classList.add(id); | 85 return target; |
174 return targetContainer; | |
175 } | 86 } |
176 | 87 |
| 88 var anchor = createElement('a'); |
177 function sanitizeUrls(value) { | 89 function sanitizeUrls(value) { |
178 var matches = value.match(/url\([^\)]*\)/g); | 90 var matches = value.match(/url\([^\)]*\)/g); |
179 if (matches !== null) { | 91 if (matches !== null) { |
180 for (var i = 0; i < matches.length; ++i) { | 92 for (var i = 0; i < matches.length; ++i) { |
181 var url = /url\(([^\)]*)\)/g.exec(matches[i])[1]; | 93 var url = /url\(([^\)]*)\)/g.exec(matches[i])[1]; |
182 var anchor = document.createElement('a'); | |
183 anchor.href = url; | 94 anchor.href = url; |
184 anchor.pathname = '...' + anchor.pathname.substring(anchor.pathname.last
IndexOf('/')); | 95 anchor.pathname = '...' + anchor.pathname.substring(anchor.pathname.last
IndexOf('/')); |
185 value = value.replace(matches[i], 'url(' + anchor.href + ')'); | 96 value = value.replace(matches[i], 'url(' + anchor.href + ')'); |
186 } | 97 } |
187 } | 98 } |
188 return value; | 99 return value; |
189 } | 100 } |
190 | 101 |
191 function serializeSVGLengthList(numberList) { | 102 function serializeSVGLengthList(numberList) { |
192 var elements = []; | 103 var elements = []; |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
247 'stdDeviation', | 158 'stdDeviation', |
248 ]; | 159 ]; |
249 | 160 |
250 function namespacedAttributeName(attributeName) { | 161 function namespacedAttributeName(attributeName) { |
251 if (attributeName === 'href') | 162 if (attributeName === 'href') |
252 return 'xlink:href'; | 163 return 'xlink:href'; |
253 return attributeName; | 164 return attributeName; |
254 } | 165 } |
255 | 166 |
256 function getAttributeValue(element, attributeName) { | 167 function getAttributeValue(element, attributeName) { |
257 if (animatedNumberOptionalNumberAttributes.indexOf(attributeName) !== -1) | 168 if (animatedNumberOptionalNumberAttributes.includes(attributeName)) |
258 return getAttributeValue(element, attributeName + 'X') + ', ' + getAttribu
teValue(element, attributeName + 'Y'); | 169 return getAttributeValue(element, attributeName + 'X') + ', ' + getAttribu
teValue(element, attributeName + 'Y'); |
259 | 170 |
260 // The attribute 'class' is exposed in IDL as 'className' | 171 // The attribute 'class' is exposed in IDL as 'className' |
261 if (attributeName === 'class') | 172 if (attributeName === 'class') |
262 attributeName = 'className'; | 173 attributeName = 'className'; |
263 | 174 |
264 // The attribute 'in' is exposed in IDL as 'in1' | 175 // The attribute 'in' is exposed in IDL as 'in1' |
265 if (attributeName === 'in') | 176 if (attributeName === 'in') |
266 attributeName = 'in1'; | 177 attributeName = 'in1'; |
267 | 178 |
(...skipping 11 matching lines...) Expand all Loading... |
279 result = element['animatedPoints']; | 190 result = element['animatedPoints']; |
280 else | 191 else |
281 result = element[attributeName].animVal; | 192 result = element[attributeName].animVal; |
282 | 193 |
283 if (!result) { | 194 if (!result) { |
284 if (attributeName === 'pathLength') | 195 if (attributeName === 'pathLength') |
285 return '0'; | 196 return '0'; |
286 if (attributeName === 'preserveAlpha') | 197 if (attributeName === 'preserveAlpha') |
287 return 'false'; | 198 return 'false'; |
288 | 199 |
289 console.log('Unknown attribute, cannot get ' + element.className.baseVal +
' ' + attributeName); | 200 console.error('Unknown attribute, cannot get ' + attributeName); |
290 return null; | 201 return null; |
291 } | 202 } |
292 | 203 |
293 if (result instanceof SVGAngle || result instanceof SVGLength) | 204 if (result instanceof SVGAngle || result instanceof SVGLength) |
294 result = result.value; | 205 result = result.value; |
295 else if (result instanceof SVGLengthList) | 206 else if (result instanceof SVGLengthList) |
296 result = serializeSVGLengthList(result); | 207 result = serializeSVGLengthList(result); |
297 else if (result instanceof SVGNumberList) | 208 else if (result instanceof SVGNumberList) |
298 result = serializeSVGNumberList(result); | 209 result = serializeSVGNumberList(result); |
299 else if (result instanceof SVGPointList) | 210 else if (result instanceof SVGPointList) |
300 result = serializeSVGPointList(result); | 211 result = serializeSVGPointList(result); |
301 else if (result instanceof SVGPreserveAspectRatio) | 212 else if (result instanceof SVGPreserveAspectRatio) |
302 result = serializeSVGPreserveAspectRatio(result); | 213 result = serializeSVGPreserveAspectRatio(result); |
303 else if (result instanceof SVGRect) | 214 else if (result instanceof SVGRect) |
304 result = serializeSVGRect(result); | 215 result = serializeSVGRect(result); |
305 else if (result instanceof SVGTransformList) | 216 else if (result instanceof SVGTransformList) |
306 result = serializeSVGTransformList(result); | 217 result = serializeSVGTransformList(result); |
307 | 218 |
308 if (typeof result !== 'string' && typeof result !== 'number' && typeof resul
t !== 'boolean') { | 219 if (typeof result !== 'string' && typeof result !== 'number' && typeof resul
t !== 'boolean') { |
309 console.log('Attribute value has unexpected type: ' + result); | 220 console.error('Attribute value has unexpected type: ' + result); |
310 } | 221 } |
311 | 222 |
312 return String(result); | 223 return String(result); |
313 } | 224 } |
314 | 225 |
315 function setAttributeValue(element, attributeName, expectation) { | 226 function setAttributeValue(element, attributeName, expectation) { |
316 if (!element[attributeName] | 227 if (!element[attributeName] |
317 && attributeName !== 'class' | 228 && attributeName !== 'class' |
318 && attributeName !== 'd' | 229 && attributeName !== 'd' |
319 && (attributeName !== 'in' || !element['in1']) | 230 && (attributeName !== 'in' || !element['in1']) |
320 && (attributeName !== 'orient' || !element['orientType']) | 231 && (attributeName !== 'orient' || !element['orientType']) |
321 && (animatedNumberOptionalNumberAttributes.indexOf(attributeName) === -1
|| !element[attributeName + 'X'])) { | 232 && (animatedNumberOptionalNumberAttributes.indexOf(attributeName) === -1
|| !element[attributeName + 'X'])) { |
322 console.log('Unknown attribute, cannot set ' + element.className.baseVal +
' ' + attributeName); | 233 console.error('Unknown attribute, cannot set ' + attributeName); |
323 return; | 234 return; |
324 } | 235 } |
325 | 236 |
326 if (attributeName === 'class') { | |
327 // Preserve the existing classes as we use them to select elements. | |
328 expectation = element['className'].baseVal + ' ' + expectation; | |
329 } | |
330 | |
331 if (attributeName.toLowerCase().indexOf('transform') === -1) { | 237 if (attributeName.toLowerCase().indexOf('transform') === -1) { |
332 var setElement = document.createElementNS(svgNamespace, 'set'); | 238 var setElement = document.createElementNS(svgNamespace, 'set'); |
333 setElement.setAttribute('attributeName', namespacedAttributeName(attribute
Name)); | 239 setElement.setAttribute('attributeName', namespacedAttributeName(attribute
Name)); |
334 setElement.setAttribute('attributeType', 'XML'); | 240 setElement.setAttribute('attributeType', 'XML'); |
335 setElement.setAttribute('to', expectation); | 241 setElement.setAttribute('to', expectation); |
336 element.appendChild(setElement); | 242 element.appendChild(setElement); |
337 } else { | 243 } else { |
338 element.setAttribute(attributeName, expectation); | 244 element.setAttribute(attributeName, expectation); |
339 } | 245 } |
340 } | 246 } |
341 | 247 |
342 function makeKeyframes(target, attributeName, params) { | 248 function createAnimateElement(attributeName, from, to) |
343 // Replace 'transform' with 'svgTransform', etc. This avoids collisions with
CSS properties or the Web Animations API (offset). | 249 { |
344 attributeName = 'svg' + attributeName[0].toUpperCase() + attributeName.slice
(1); | 250 var animateElement; |
| 251 if (attributeName.toLowerCase().includes('transform')) { |
| 252 from = from.split(')'); |
| 253 to = to.split(')'); |
| 254 // Discard empty string at end. |
| 255 from.pop(); |
| 256 to.pop(); |
345 | 257 |
346 var keyframes = [{}, {}]; | 258 // SMIL requires separate animateTransform elements for each transform in
the list. |
347 if (attributeName === 'svgClass') { | 259 if (from.length !== 1 || to.length !== 1) { |
348 // Preserve the existing classes as we use them to select elements. | 260 return null; |
349 keyframes[0][attributeName] = target['className'].baseVal + ' ' + params.f
rom; | 261 } |
350 keyframes[1][attributeName] = target['className'].baseVal + ' ' + params.t
o; | 262 |
| 263 from = from[0].split('('); |
| 264 to = to[0].split('('); |
| 265 if (from[0].trim() !== to[0].trim()) { |
| 266 return null; |
| 267 } |
| 268 |
| 269 animateElement = document.createElementNS(svgNamespace, 'animateTransform'
); |
| 270 animateElement.setAttribute('type', from[0].trim()); |
| 271 animateElement.setAttribute('from', from[1]); |
| 272 animateElement.setAttribute('to', to[1]); |
351 } else { | 273 } else { |
352 keyframes[0][attributeName] = params.from; | 274 animateElement = document.createElementNS(svgNamespace, 'animate'); |
353 keyframes[1][attributeName] = params.to; | |
354 } | |
355 return keyframes; | |
356 } | |
357 | |
358 function appendAnimate(target, attributeName, from, to) | |
359 { | |
360 var animateElement = document.createElementNS(svgNamespace, 'animate'); | |
361 animateElement.setAttribute('attributeName', namespacedAttributeName(attribu
teName)); | |
362 animateElement.setAttribute('attributeType', 'XML'); | |
363 if (attributeName === 'class') { | |
364 // Preserve the existing classes as we use them to select elements. | |
365 animateElement.setAttribute('from', target['className'].baseVal + ' ' + fr
om); | |
366 animateElement.setAttribute('to', target['className'].baseVal + ' ' + to); | |
367 } else { | |
368 animateElement.setAttribute('from', from); | 275 animateElement.setAttribute('from', from); |
369 animateElement.setAttribute('to', to); | 276 animateElement.setAttribute('to', to); |
370 } | 277 } |
| 278 |
| 279 animateElement.setAttribute('attributeName', namespacedAttributeName(attribu
teName)); |
| 280 animateElement.setAttribute('attributeType', 'XML'); |
371 animateElement.setAttribute('begin', '0'); | 281 animateElement.setAttribute('begin', '0'); |
372 animateElement.setAttribute('dur', '1'); | 282 animateElement.setAttribute('dur', '1'); |
373 animateElement.setAttribute('fill', 'freeze'); | 283 animateElement.setAttribute('fill', 'freeze'); |
374 target.appendChild(animateElement); | 284 return animateElement; |
375 } | 285 } |
376 | 286 |
377 function appendAnimateTransform(target, attributeName, from, to) | 287 function createTestTarget(method, description, container, params, expectation)
{ |
378 { | 288 var target = createTarget(container); |
379 var animateElement = document.createElementNS(svgNamespace, 'animate'); | 289 var expected = createTarget(container); |
380 animateElement.setAttribute('attributeName', namespacedAttributeName(attribu
teName)); | 290 setAttributeValue(expected, params.property, expectation.is); |
381 animateElement.setAttribute('attributeType', 'XML'); | 291 |
382 if (attributeName === 'class') { | 292 target.interpolate = function() { |
383 // Preserve the existing classes as we use them to select elements. | 293 switch (method) { |
384 animateElement.setAttribute('from', target['className'].baseVal + ' ' + fr
om); | 294 case 'SMIL': |
385 animateElement.setAttribute('to', target['className'].baseVal + ' ' + to); | 295 var animateElement = createAnimateElement(params.property, params.from,
params.to); |
386 } else { | 296 if (animateElement) { |
387 animateElement.setAttribute('from', from); | 297 target.appendChild(animateElement); |
388 animateElement.setAttribute('to', to); | 298 target.container.pauseAnimations(); |
389 } | 299 target.container.setCurrentTime(expectation.at); |
390 animateElement.setAttribute('begin', '0'); | 300 } else { |
391 animateElement.setAttribute('dur', '1'); | 301 console.warn(`Unable to test SMIL from ${params.from} to ${params.to}`
); |
392 animateElement.setAttribute('fill', 'freeze'); | 302 target.container.remove(); |
393 target.appendChild(animateElement); | 303 target.measure = function() {}; |
| 304 } |
| 305 break; |
| 306 case 'Web Animations': |
| 307 // Replace 'transform' with 'svgTransform', etc. This avoids collisions
with CSS properties or the Web Animations API (offset). |
| 308 var prefixedProperty = 'svg' + params.property[0].toUpperCase() + params
.property.slice(1); |
| 309 target.animate([ |
| 310 {[prefixedProperty]: params.from}, |
| 311 {[prefixedProperty]: params.to}, |
| 312 ], { |
| 313 fill: 'forwards', |
| 314 duration: 1, |
| 315 easing: createEasing(expectation.at), |
| 316 delay: -0.5, |
| 317 iterations: 0.5, |
| 318 }); |
| 319 break; |
| 320 default: |
| 321 console.error('Unknown test method: ' + method); |
| 322 } |
| 323 }; |
| 324 |
| 325 target.measure = function() { |
| 326 test(function() { |
| 327 assert_equals( |
| 328 normalizeValue(getAttributeValue(target, params.property)), |
| 329 normalizeValue(getAttributeValue(expected, params.property))); |
| 330 }, `${method}: ${description} at (${expectation.at}) is [${expectation.is}
]`); |
| 331 }; |
| 332 |
| 333 return target; |
394 } | 334 } |
395 | 335 |
396 // Append animateTransform elements to parents of target. | 336 function createTestTargets(interpolationTests, container) { |
397 function appendAnimateTransform(target, attributeName, from, to) | 337 var targets = []; |
398 { | 338 for (var interpolationTest of interpolationTests) { |
399 var currentElement = target; | 339 var params = interpolationTest.params; |
400 var parents = []; | 340 var description = `Interpolate attribute <${params.property}> from [${para
ms.from}] to [${params.to}]`; |
401 from = from.split(')'); | 341 var expectations = interpolationTest.expectations; |
402 to = to.split(')'); | |
403 // Discard empty string at end. | |
404 from.pop(); | |
405 to.pop(); | |
406 | 342 |
407 // SMIL requires separate animateTransform elements for each transform in th
e list. | 343 for (var method of ['SMIL', 'Web Animations']) { |
408 if (from.length !== 1 || to.length !== 1) | 344 createElement('pre', container).textContent = `${method}: ${description}
`; |
409 return; | 345 var smilContainer = createElement('div', container); |
410 | 346 for (var expectation of expectations) { |
411 from = from[0].split('('); | 347 if (method === 'SMIL' && (expectation.at < 0 || expectation.at > 1)) { |
412 to = to[0].split('('); | 348 continue; |
413 if (from[0].trim() !== to[0].trim()) | 349 } |
414 return; | 350 targets.push(createTestTarget(method, description, smilContainer, para
ms, expectation)); |
415 | 351 } |
416 var animateTransformElement = document.createElementNS(svgNamespace, 'animat
eTransform'); | 352 } |
417 animateTransformElement.setAttribute('attributeName', namespacedAttributeNam
e(attributeName)); | 353 } |
418 animateTransformElement.setAttribute('attributeType', 'XML'); | 354 return targets; |
419 animateTransformElement.setAttribute('type', from[0].trim()); | |
420 animateTransformElement.setAttribute('from', from[1]); | |
421 animateTransformElement.setAttribute('to', to[1]); | |
422 animateTransformElement.setAttribute('begin', '0'); | |
423 animateTransformElement.setAttribute('dur', '1'); | |
424 animateTransformElement.setAttribute('fill', 'freeze'); | |
425 target.appendChild(animateTransformElement); | |
426 } | 355 } |
427 | 356 |
428 function makeInterpolationTest(testType, fraction, testId, caseId, params, exp
ectation) { | 357 function runTests() { |
429 var attributeName = convertPropertyToCamelCase(params.property); | 358 return new Promise((resolve) => { |
| 359 var container = createElement('div', document.body); |
| 360 var targets = createTestTargets(interpolationTests, container); |
430 | 361 |
431 var targetContainer = createTargetContainer(caseId); | 362 requestAnimationFrame(() => { |
432 var target = targetContainer.querySelector('.target') || targetContainer; | 363 for (var target of targets) { |
433 target.classList.add(testType); | 364 target.interpolate(); |
434 var replicaContainer, replica; | |
435 { | |
436 replicaContainer = createTargetContainer(caseId); | |
437 replica = replicaContainer.querySelector('.target') || replicaContainer; | |
438 replica.classList.add('replica'); | |
439 setAttributeValue(replica, params.property, expectation); | |
440 } | |
441 target.testType = testType; | |
442 target.getResultString = function() { | |
443 var value = getAttributeValue(this, params.property); | |
444 var result = ''; | |
445 var reason = ''; | |
446 var property = convertPropertyToCamelCase(params.property); | |
447 { | |
448 var parsedExpectation = getAttributeValue(replica, params.property); | |
449 if (attributeName === 'class') { | |
450 parsedExpectation = parsedExpectation.replace('replica', testType); | |
451 } | 365 } |
452 // console.log('expected ' + parsedExpectation + ', actual ' + value); | |
453 var pass = normalizeValue(value) === normalizeValue(parsedExpectation); | |
454 result = pass ? 'PASS: ' : 'FAIL: '; | |
455 reason = pass ? '' : ', expected [' + expectation + ']' + | |
456 (expectation === parsedExpectation ? '' : ' (parsed as [' + sanitize
Urls(roundNumbers(parsedExpectation)) + '])'); | |
457 value = pass ? expectation : sanitizeUrls(value); | |
458 } | |
459 return result + property + ' from [' + params.from + '] to ' + | |
460 '[' + params.to + '] was [' + value + ']' + | |
461 ' at ' + fraction + reason; | |
462 }; | |
463 var easing = createEasing(fraction); | |
464 testCount++; | |
465 { | |
466 fragmentAttachedListeners.push(function() { | |
467 if (testType === 'smil') { | |
468 if (attributeName.toLowerCase().indexOf('transform') === -1) | |
469 appendAnimate(target, attributeName, params.from, params.to); | |
470 else | |
471 appendAnimateTransform(target, attributeName, params.from, params.to
); | |
472 | 366 |
473 targetContainer.pauseAnimations(); | 367 requestAnimationFrame(() => { |
474 targetContainer.setCurrentTime(fraction); | 368 for (var target of targets) { |
475 } else if (testType === 'web-animations' && target.animate) { | 369 target.measure(); |
476 target.animate(makeKeyframes(target, attributeName, params), { | 370 } |
477 fill: 'forwards', | 371 |
478 duration: 1, | 372 if (window.testRunner) { |
479 easing: easing, | 373 container.style.display = 'none'; |
480 delay: -0.5, | 374 } |
481 iterations: 0.5, | 375 |
482 }); | 376 resolve(); |
483 } | 377 }); |
484 animationEnded(); | |
485 }); | 378 }); |
486 } | 379 }); |
487 var testFragment = document.createDocumentFragment(); | |
488 testFragment.appendChild(targetContainer); | |
489 testFragment.appendChild(replicaContainer); | |
490 testFragment.appendChild(document.createTextNode('\n')); | |
491 return testFragment; | |
492 } | 380 } |
493 | 381 |
494 var finished = false; | 382 function loadScript(url) { |
495 function finishTest() { | 383 return new Promise(function(resolve) { |
496 finished = true; | 384 var script = createElement('script', document.head); |
497 dumpResults(); | 385 script.src = url; |
498 if (window.testRunner) { | 386 script.onload = resolve; |
499 var results = document.querySelector('#results'); | 387 }); |
500 document.documentElement.textContent = ''; | |
501 document.documentElement.appendChild(results); | |
502 testRunner.dumpAsText(); | |
503 | |
504 testRunner.notifyDone(); | |
505 } | |
506 } | 388 } |
507 | 389 |
508 if (window.testRunner) { | 390 loadScript('../../resources/testharness.js').then(() => { |
509 testRunner.waitUntilDone(); | 391 return loadScript('../../resources/testharnessreport.js'); |
510 } | 392 }).then(() => { |
511 | 393 var asyncHandle = async_test('This test uses interpolation-test.js.') |
512 function isLastAnimationEvent() { | 394 requestAnimationFrame(() => { |
513 return !finished && animationEventCount === testCount; | 395 runTests().then(() => asyncHandle.done()); |
514 } | 396 }); |
515 | 397 }); |
516 function animationEnded() { | |
517 animationEventCount++; | |
518 if (!isLastAnimationEvent()) { | |
519 return; | |
520 } | |
521 requestAnimationFrame(finishTest); | |
522 } | |
523 | |
524 if (!window.testRunner) { | |
525 setTimeout(function() { | |
526 if (finished) { | |
527 return; | |
528 } | |
529 finishTest(); | |
530 }, 5000); | |
531 } | |
532 | 398 |
533 window.assertAttributeInterpolation = assertAttributeInterpolation; | 399 window.assertAttributeInterpolation = assertAttributeInterpolation; |
534 })(); | 400 })(); |
OLD | NEW |