| 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 |