OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (C) 2013 Google Inc. All rights reserved. |
| 3 * |
| 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions are |
| 6 * met: |
| 7 * |
| 8 * * Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. |
| 10 * * Redistributions in binary form must reproduce the above |
| 11 * copyright notice, this list of conditions and the following disclaimer |
| 12 * in the documentation and/or other materials provided with the |
| 13 * distribution. |
| 14 * * Neither the name of Google Inc. nor the names of its |
| 15 * contributors may be used to endorse or promote products derived from |
| 16 * this software without specific prior written permission. |
| 17 * |
| 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ |
| 30 |
| 31 /* |
| 32 * This script is intended to be used for constructing layout tests which |
| 33 * exercise the interpolation functionaltiy of the animation system. |
| 34 * Tests which run using this script should be portable across browsers. |
| 35 * |
| 36 * The following function is exported: |
| 37 * * assertInterpolation({property: x, from: y, to: z}, [{at: fraction, is: val
ue}]) |
| 38 * Constructs a test case which for each fraction will output a PASS |
| 39 * or FAIL depending on whether the interpolated result matches |
| 40 * 'value'. Replica elements are constructed to aid eyeballing test |
| 41 * results. |
| 42 */ |
| 43 'use strict'; |
| 44 (function() { |
| 45 var endEvent = 'animationend'; |
| 46 var testCount = 0; |
| 47 var animationEventCount = 0; |
| 48 var durationSeconds = 0; |
| 49 var iterationCount = 0.5; |
| 50 var delaySeconds = 0; |
| 51 var fragment = document.createDocumentFragment(); |
| 52 var fragmentAttachedListeners = []; |
| 53 var style = document.createElement('style'); |
| 54 var afterTestCallback = null; |
| 55 fragment.appendChild(style); |
| 56 |
| 57 var tests = document.createElement('div'); |
| 58 tests.id = 'interpolation-tests'; |
| 59 tests.textContent = 'Interpolation Tests:'; |
| 60 fragment.appendChild(tests); |
| 61 |
| 62 var updateScheduled = false; |
| 63 function maybeScheduleUpdate() { |
| 64 if (updateScheduled) { |
| 65 return; |
| 66 } |
| 67 updateScheduled = true; |
| 68 setTimeout(function() { |
| 69 updateScheduled = false; |
| 70 document.body.appendChild(fragment); |
| 71 fragmentAttachedListeners.forEach(function(listener) {listener();}); |
| 72 }, 0); |
| 73 } |
| 74 |
| 75 function evaluateTests() { |
| 76 var targets = document.querySelectorAll('.target.active'); |
| 77 for (var i = 0; i < targets.length; i++) { |
| 78 targets[i].evaluate(); |
| 79 } |
| 80 } |
| 81 |
| 82 function afterTest(callback) { |
| 83 afterTestCallback = callback; |
| 84 } |
| 85 |
| 86 // Constructs a timing function which produces 'y' at x = 0.5 |
| 87 function createEasing(y) { |
| 88 // FIXME: if 'y' is > 0 and < 1 use a linear timing function and allow |
| 89 // 'x' to vary. Use a bezier only for values < 0 or > 1. |
| 90 if (y == 0) { |
| 91 return 'steps(1, end)'; |
| 92 } |
| 93 if (y == 1) { |
| 94 return 'steps(1, start)'; |
| 95 } |
| 96 if (y == 0.5) { |
| 97 return 'steps(2, end)'; |
| 98 } |
| 99 // Approximate using a bezier. |
| 100 var b = (8 * y - 1) / 6; |
| 101 return 'cubic-bezier(0, ' + b + ', 1, ' + b + ')'; |
| 102 } |
| 103 |
| 104 function createTestContainer(description, className) { |
| 105 var testContainer = document.createElement('div'); |
| 106 testContainer.setAttribute('description', description); |
| 107 testContainer.classList.add('test'); |
| 108 if (className) { |
| 109 testContainer.classList.add(className); |
| 110 } |
| 111 return testContainer; |
| 112 } |
| 113 |
| 114 function convertPropertyToCamelCase(property) { |
| 115 return property.replace(/^-/, '').replace(/-\w/g, function(m) {return m[1].t
oUpperCase();}); |
| 116 } |
| 117 |
| 118 function describeTest(params) { |
| 119 return convertPropertyToCamelCase(params.property) + ': from [' + params.fro
m + '] to [' + params.to + ']'; |
| 120 } |
| 121 |
| 122 var nextKeyframeId = 0; |
| 123 function assertInterpolation(params, expectations) { |
| 124 var testId = 'test-' + ++nextKeyframeId; |
| 125 var nextCaseId = 0; |
| 126 var testContainer = createTestContainer(describeTest(params), testId); |
| 127 tests.appendChild(testContainer); |
| 128 expectations.forEach(function(expectation) { |
| 129 testContainer.appendChild(makeInterpolationTest( |
| 130 expectation.at, testId, 'case-' + ++nextCaseId, params, expectation.
is)); |
| 131 }); |
| 132 maybeScheduleUpdate(); |
| 133 } |
| 134 |
| 135 function roundNumbers(value) { |
| 136 // Round numbers to two decimal places. |
| 137 return value.replace(/-?\d*\.\d+/g, function(n) { |
| 138 return (parseFloat(n).toFixed(2)). |
| 139 replace(/\.\d+/, function(m) { |
| 140 return m.replace(/0+$/, ''); |
| 141 }). |
| 142 replace(/\.$/, ''). |
| 143 replace(/^-0$/, '0'); |
| 144 }); |
| 145 } |
| 146 |
| 147 function normalizeValue(value) { |
| 148 return roundNumbers(value). |
| 149 // Place whitespace between tokens. |
| 150 replace(/([\w\d.]+|[^\s])/g, '$1 '). |
| 151 replace(/\s+/g, ' '); |
| 152 } |
| 153 |
| 154 function createTargetContainer(id) { |
| 155 var targetContainer = document.createElement('div'); |
| 156 var template = document.querySelector('#target-template'); |
| 157 if (template) { |
| 158 if (template.content) |
| 159 targetContainer.appendChild(template.content.cloneNode(true)); |
| 160 else if (template.querySelector('div')) |
| 161 targetContainer.appendChild(template.querySelector('div').cloneNode(true
)); |
| 162 else |
| 163 targetContainer.appendChild(template.cloneNode(true)); |
| 164 // Remove whitespace text nodes at start / end. |
| 165 while (targetContainer.firstChild.nodeType != Node.ELEMENT_NODE && !/\S/.t
est(targetContainer.firstChild.nodeValue)) { |
| 166 targetContainer.removeChild(targetContainer.firstChild); |
| 167 } |
| 168 while (targetContainer.lastChild.nodeType != Node.ELEMENT_NODE && !/\S/.te
st(targetContainer.lastChild.nodeValue)) { |
| 169 targetContainer.removeChild(targetContainer.lastChild); |
| 170 } |
| 171 // If the template contains just one element, use that rather than a wrapp
er div. |
| 172 if (targetContainer.children.length == 1 && targetContainer.childNodes.len
gth == 1) { |
| 173 targetContainer = targetContainer.firstChild; |
| 174 targetContainer.parentNode.removeChild(targetContainer); |
| 175 } |
| 176 } |
| 177 var target = targetContainer.querySelector('.target') || targetContainer; |
| 178 target.classList.add('target'); |
| 179 target.classList.add(id); |
| 180 return targetContainer; |
| 181 } |
| 182 |
| 183 function sanitizeUrls(value) { |
| 184 var matches = value.match(/url\([^\)]*\)/g); |
| 185 if (matches !== null) { |
| 186 for (var i = 0; i < matches.length; ++i) { |
| 187 var url = /url\(([^\)]*)\)/g.exec(matches[i])[1]; |
| 188 var anchor = document.createElement('a'); |
| 189 anchor.href = url; |
| 190 anchor.pathname = '...' + anchor.pathname.substring(anchor.pathname.last
IndexOf('/')); |
| 191 value = value.replace(matches[i], 'url(' + anchor.href + ')'); |
| 192 } |
| 193 } |
| 194 return value; |
| 195 } |
| 196 |
| 197 function makeInterpolationTest(fraction, testId, caseId, params, expectation)
{ |
| 198 var t = async_test(describeTest(params) + ' at ' + fraction); |
| 199 var targetContainer = createTargetContainer(caseId); |
| 200 var target = targetContainer.querySelector('.target') || targetContainer; |
| 201 target.classList.add('active'); |
| 202 var replicaContainer, replica; |
| 203 replicaContainer = createTargetContainer(caseId); |
| 204 replica = replicaContainer.querySelector('.target') || replicaContainer; |
| 205 replica.classList.add('replica'); |
| 206 replica.style.setProperty(params.property, expectation); |
| 207 if (params.prefixedProperty) { |
| 208 for (var i = 0; i < params.prefixedProperty.length; i++) { |
| 209 replica.style.setProperty(params.prefixedProperty[i], expectation); |
| 210 } |
| 211 } |
| 212 |
| 213 target.evaluate = function() { |
| 214 var target = this; |
| 215 t.step(function() { |
| 216 window.CSS && assert_true(CSS.supports(params.property, expectation)); |
| 217 var value = getComputedStyle(target).getPropertyValue(params.property); |
| 218 var property = params.property; |
| 219 if (params.prefixedProperty) { |
| 220 var i = 0; |
| 221 while (i < params.prefixedProperty.length && !value) { |
| 222 property = params.prefixedProperty[i++]; |
| 223 value = getComputedStyle(target).getPropertyValue(property) |
| 224 } |
| 225 } |
| 226 if (!value) { |
| 227 assert_false(params.property + ' not supported by this browser'); |
| 228 } |
| 229 var originalValue = value; |
| 230 var parsedExpectation = getComputedStyle(replica).getPropertyValue(prope
rty); |
| 231 assert_equals(normalizeValue(originalValue), normalizeValue(parsedExpect
ation)); |
| 232 t.done(); |
| 233 }); |
| 234 }; |
| 235 |
| 236 var easing = createEasing(fraction); |
| 237 testCount++; |
| 238 var keyframes = [{}, {}]; |
| 239 keyframes[0][convertPropertyToCamelCase(params.property)] = params.from; |
| 240 keyframes[1][convertPropertyToCamelCase(params.property)] = params.to; |
| 241 fragmentAttachedListeners.push(function() { |
| 242 target.animate(keyframes, { |
| 243 fill: 'forwards', |
| 244 duration: 1, |
| 245 easing: easing, |
| 246 delay: -0.5, |
| 247 iterations: 0.5, |
| 248 }); |
| 249 animationEnded(); |
| 250 }); |
| 251 var testFragment = document.createDocumentFragment(); |
| 252 testFragment.appendChild(targetContainer); |
| 253 replica && testFragment.appendChild(replicaContainer); |
| 254 testFragment.appendChild(document.createTextNode('\n')); |
| 255 return testFragment; |
| 256 } |
| 257 |
| 258 var finished = false; |
| 259 function finishTest() { |
| 260 finished = true; |
| 261 evaluateTests(); |
| 262 if (afterTestCallback) { |
| 263 afterTestCallback(); |
| 264 } |
| 265 if (window.testRunner) { |
| 266 var results = document.querySelector('#results'); |
| 267 document.documentElement.textContent = ''; |
| 268 document.documentElement.appendChild(results); |
| 269 testRunner.dumpAsText(); |
| 270 testRunner.notifyDone(); |
| 271 } |
| 272 } |
| 273 |
| 274 if (window.testRunner) { |
| 275 testRunner.waitUntilDone(); |
| 276 } |
| 277 |
| 278 function isLastAnimationEvent() { |
| 279 return !finished && animationEventCount === testCount; |
| 280 } |
| 281 |
| 282 function animationEnded() { |
| 283 animationEventCount++; |
| 284 if (!isLastAnimationEvent()) { |
| 285 return; |
| 286 } |
| 287 finishTest(); |
| 288 } |
| 289 |
| 290 document.documentElement.addEventListener(endEvent, animationEnded); |
| 291 |
| 292 if (!window.testRunner) { |
| 293 setTimeout(function() { |
| 294 if (finished) { |
| 295 return; |
| 296 } |
| 297 finishTest(); |
| 298 }, 10000); |
| 299 } |
| 300 |
| 301 window.assertInterpolation = assertInterpolation; |
| 302 window.afterTest = afterTest; |
| 303 })(); |
OLD | NEW |