| 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 | 4 |
| 5 /* | 5 /* |
| 6 Exported function: | 6 Exported functions: |
| 7 assertResponsive | 7 assertCSSResponsive |
| 8 assertSVGResponsive |
| 8 | 9 |
| 9 Call signature: | 10 Options format: { |
| 10 assertResponsive({ | 11 ?targetTag: <Target element tag name>, |
| 11 property: <CSS Property>, | 12 property: <Property/Attribute name>, |
| 12 ?from: <CSS Value>, | 13 ?getter(target): <Reads animated value from target>, |
| 13 ?to: <CSS Value>, | 14 ?from: <Value>, |
| 15 ?to: <Value>, |
| 14 configurations: [{ | 16 configurations: [{ |
| 15 state: { | 17 state: { |
| 16 ?underlying: <CSS Value>, | 18 ?underlying: <Value>, |
| 17 ?inherited: <CSS Value>, | 19 ?inherited: <CSS Value>, |
| 18 }, | 20 }, |
| 19 expect: [ | 21 expect: [ |
| 20 { at: <Float>, is: <CSS Value> } | 22 { at: <Float>, is: <Value> }, |
| 21 ], | 23 ], |
| 22 }], | 24 }], |
| 23 }) | 25 } |
| 24 | 26 |
| 25 Description: | 27 Description: |
| 26 assertResponsive takes a property specific interpolation and a list of style | 28 assertCSSResponsive() and assertSVGResponsive() take a property |
| 27 configurations with interpolation expectations that apply to each | 29 specific interpolation and a list of style configurations with interpolation |
| 28 configuration. | 30 expectations that apply to each configuration. |
| 29 It starts the interpolation in every configuration, changes the | 31 It starts the interpolation in every configuration, changes the |
| 30 state to every other configuration (n * (n - 1) complexity) and asserts that | 32 state to every other configuration (n * (n - 1) complexity) and asserts that |
| 31 each destination configuration's expectations are met. | 33 each destination configuration's expectations are met. |
| 32 Each animation target can be assigned custom styles via the ".target" selector. | 34 Each animation target can be assigned custom styles via the ".target" selector. |
| 33 This test is designed to catch stale interpolation caches. | 35 This test is designed to catch stale interpolation caches. |
| 34 */ | 36 */ |
| 35 | 37 |
| 36 (function() { | 38 (function() { |
| 37 'use strict'; | 39 'use strict'; |
| 38 var pendingResponsiveTests = []; | 40 var pendingResponsiveTests = []; |
| 41 var htmlNamespace = 'http://www.w3.org/1999/xhtml'; |
| 42 var svgNamespace = 'http://www.w3.org/2000/svg'; |
| 39 | 43 |
| 40 function assertResponsive(options) { | 44 function assertCSSResponsive(options) { |
| 41 pendingResponsiveTests.push(options); | 45 pendingResponsiveTests.push({ |
| 46 options, |
| 47 bindings: { |
| 48 prefixProperty(property) { |
| 49 return property; |
| 50 }, |
| 51 createTargetContainer(container) { |
| 52 return createElement('div', container); |
| 53 }, |
| 54 createTarget(container) { |
| 55 return createElement('div', container, 'target'); |
| 56 }, |
| 57 setValue(target, property, value) { |
| 58 target.style[property] = value; |
| 59 }, |
| 60 getAnimatedValue(target, property) { |
| 61 return getComputedStyle(target)[property]; |
| 62 }, |
| 63 }, |
| 64 }); |
| 65 } |
| 66 |
| 67 function assertSVGResponsive(options) { |
| 68 pendingResponsiveTests.push({ |
| 69 options, |
| 70 bindings: { |
| 71 prefixProperty(property) { |
| 72 return 'svg' + property[0].toUpperCase() + property.slice(1); |
| 73 }, |
| 74 createTargetContainer(container) { |
| 75 var svgRoot = createElement('svg', container, 'svg-root', svgNamespace); |
| 76 svgRoot.setAttribute('width', 0); |
| 77 svgRoot.setAttribute('height', 0); |
| 78 return svgRoot; |
| 79 }, |
| 80 createTarget(targetContainer) { |
| 81 console.assert(options.targetTag); |
| 82 return createElement(options.targetTag, targetContainer, 'target', svgNa
mespace); |
| 83 }, |
| 84 setValue(target, property, value) { |
| 85 target.setAttribute(property, value); |
| 86 }, |
| 87 getAnimatedValue(target, property) { |
| 88 return options.getter ? options.getter(target) : target[property].animVa
l; |
| 89 }, |
| 90 }, |
| 91 }); |
| 42 } | 92 } |
| 43 | 93 |
| 44 function createStateTransitions(configurations) { | 94 function createStateTransitions(configurations) { |
| 45 var stateTransitions = []; | 95 var stateTransitions = []; |
| 46 for (var i = 0; i < configurations.length; i++) { | 96 for (var i = 0; i < configurations.length; i++) { |
| 47 var beforeConfiguration = configurations[i]; | 97 var beforeConfiguration = configurations[i]; |
| 48 for (var j = 0; j < configurations.length; j++) { | 98 for (var j = 0; j < configurations.length; j++) { |
| 49 var afterConfiguration = configurations[j]; | 99 var afterConfiguration = configurations[j]; |
| 50 if (j != i) { | 100 if (j != i) { |
| 51 stateTransitions.push({ | 101 stateTransitions.push({ |
| 52 before: beforeConfiguration, | 102 before: beforeConfiguration, |
| 53 after: afterConfiguration, | 103 after: afterConfiguration, |
| 54 }); | 104 }); |
| 55 } | 105 } |
| 56 } | 106 } |
| 57 } | 107 } |
| 58 return stateTransitions; | 108 return stateTransitions; |
| 59 } | 109 } |
| 60 | 110 |
| 61 function createElement(tag, container, className) { | 111 function createElement(tag, container, className, namespace) { |
| 62 var element = document.createElement(tag); | 112 var element = document.createElementNS(namespace || htmlNamespace, tag); |
| 63 if (container) { | 113 if (container) { |
| 64 container.appendChild(element); | 114 container.appendChild(element); |
| 65 } | 115 } |
| 66 if (className) { | 116 if (className) { |
| 67 element.classList.add(className); | 117 element.classList.add(className); |
| 68 } | 118 } |
| 69 return element; | 119 return element; |
| 70 } | 120 } |
| 71 | 121 |
| 72 function createTargets(n, container) { | 122 function createTargets(bindings, n, container) { |
| 73 var targets = []; | 123 var targets = []; |
| 74 for (var i = 0; i < n; i++) { | 124 for (var i = 0; i < n; i++) { |
| 75 targets.push(createElement('div', container, 'target')); | 125 targets.push(bindings.createTarget(container)); |
| 76 } | 126 } |
| 77 return targets; | 127 return targets; |
| 78 } | 128 } |
| 79 | 129 |
| 80 function setState(targets, property, state) { | 130 function setState(bindings, targets, property, state) { |
| 81 if (state.inherited) { | 131 if (state.inherited) { |
| 82 var parent = targets[0].parentElement; | 132 var parent = targets[0].parentElement; |
| 83 console.assert(targets.every(function(target) { return target.parentElement
=== parent; })); | 133 console.assert(targets.every(function(target) { return target.parentElement
=== parent; })); |
| 84 parent.style[property] = state.inherited; | 134 bindings.setValue(parent, property, state.inherited); |
| 85 } | 135 } |
| 86 if (state.underlying) { | 136 if (state.underlying) { |
| 87 for (var target of targets) { | 137 for (var target of targets) { |
| 88 target.style[property] = state.underlying; | 138 bindings.setValue(target, property, state.underlying); |
| 89 } | 139 } |
| 90 } | 140 } |
| 91 } | 141 } |
| 92 | 142 |
| 93 function keyframeText(options, keyframeName) { | 143 function keyframeText(options, keyframeName) { |
| 94 return (keyframeName in options) ? `[${options[keyframeName]}]` : 'neutral'; | 144 return (keyframeName in options) ? `[${options[keyframeName]}]` : 'neutral'; |
| 95 } | 145 } |
| 96 | 146 |
| 97 function createKeyframes(options) { | 147 function createKeyframes(prefixedProperty, options) { |
| 98 var keyframes = []; | 148 var keyframes = []; |
| 99 if ('from' in options) { | 149 if ('from' in options) { |
| 100 keyframes.push({ | 150 keyframes.push({ |
| 101 offset: 0, | 151 offset: 0, |
| 102 [options.property]: options.from, | 152 [prefixedProperty]: options.from, |
| 103 }); | 153 }); |
| 104 } | 154 } |
| 105 if ('to' in options) { | 155 if ('to' in options) { |
| 106 keyframes.push({ | 156 keyframes.push({ |
| 107 offset: 1, | 157 offset: 1, |
| 108 [options.property]: options.to, | 158 [prefixedProperty]: options.to, |
| 109 }); | 159 }); |
| 110 } | 160 } |
| 111 return keyframes; | 161 return keyframes; |
| 112 } | 162 } |
| 113 | 163 |
| 114 function startPausedAnimations(targets, keyframes, fractions) { | 164 function startPausedAnimations(targets, keyframes, fractions) { |
| 115 console.assert(targets.length == fractions.length); | 165 console.assert(targets.length == fractions.length); |
| 116 for (var i = 0; i < targets.length; i++) { | 166 for (var i = 0; i < targets.length; i++) { |
| 117 var target = targets[i]; | 167 var target = targets[i]; |
| 118 var fraction = fractions[i]; | 168 var fraction = fractions[i]; |
| 119 console.assert(fraction >= 0 && fraction < 1); | 169 console.assert(fraction >= 0 && fraction < 1); |
| 120 var animation = target.animate(keyframes, 1); | 170 var animation = target.animate(keyframes, 1); |
| 121 animation.currentTime = fraction; | 171 animation.currentTime = fraction; |
| 122 animation.pause(); | 172 animation.pause(); |
| 123 } | 173 } |
| 124 } | 174 } |
| 125 | 175 |
| 126 function runPendingResponsiveTests() { | 176 function runPendingResponsiveTests() { |
| 127 var stateTransitionTests = []; | 177 return new Promise(function(resolve) { |
| 128 pendingResponsiveTests.forEach(function(options) { | 178 var stateTransitionTests = []; |
| 129 var property = options.property; | 179 pendingResponsiveTests.forEach(function(responsiveTest) { |
| 130 var from = options.from; | 180 var options = responsiveTest.options; |
| 131 var to = options.to; | 181 var bindings = responsiveTest.bindings; |
| 132 var keyframes = createKeyframes(options); | 182 var property = options.property; |
| 133 var fromText = keyframeText(options, 'from'); | 183 var prefixedProperty = bindings.prefixProperty(property); |
| 134 var toText = keyframeText(options, 'to'); | 184 var from = options.from; |
| 185 var to = options.to; |
| 186 var keyframes = createKeyframes(prefixedProperty, options); |
| 187 var fromText = keyframeText(options, 'from'); |
| 188 var toText = keyframeText(options, 'to'); |
| 135 | 189 |
| 136 var stateTransitions = createStateTransitions(options.configurations); | 190 var stateTransitions = createStateTransitions(options.configurations); |
| 137 stateTransitions.forEach(function(stateTransition) { | 191 stateTransitions.forEach(function(stateTransition) { |
| 138 var before = stateTransition.before; | 192 var before = stateTransition.before; |
| 139 var after = stateTransition.after; | 193 var after = stateTransition.after; |
| 140 var container = createElement('div', document.body); | 194 var container = bindings.createTargetContainer(document.body); |
| 141 var targets = createTargets(after.expect.length, container); | 195 var targets = createTargets(bindings, after.expect.length, container); |
| 142 | 196 |
| 143 setState(targets, property, before.state); | 197 setState(bindings, targets, property, before.state); |
| 144 startPausedAnimations(targets, keyframes, after.expect.map(function(expect
ation) { return expectation.at; })); | 198 startPausedAnimations(targets, keyframes, after.expect.map(function(expe
ctation) { return expectation.at; })); |
| 145 stateTransitionTests.push({ | 199 stateTransitionTests.push({ |
| 146 applyStateTransition() { | 200 applyStateTransition() { |
| 147 setState(targets, property, after.state); | 201 setState(bindings, targets, property, after.state); |
| 148 }, | 202 }, |
| 149 assert() { | 203 assert() { |
| 150 for (var i = 0; i < targets.length; i++) { | 204 for (var i = 0; i < targets.length; i++) { |
| 151 var target = targets[i]; | 205 var target = targets[i]; |
| 152 var expectation = after.expect[i]; | 206 var expectation = after.expect[i]; |
| 153 var actual = getComputedStyle(target)[property]; | 207 var actual = bindings.getAnimatedValue(target, property); |
| 154 test(function() { | 208 test(function() { |
| 155 assert_equals(actual, expectation.is); | 209 assert_equals(actual, expectation.is); |
| 156 }, `Animation on property <${property}> from ${fromText} to ${toText
} with ${JSON.stringify(before.state)} changed to ${JSON.stringify(after.state)}
at (${expectation.at}) is [${expectation.is}]`); | 210 }, `Animation on property <${prefixedProperty}> from ${fromText} t
o ${toText} with ${JSON.stringify(before.state)} changed to ${JSON.stringify(aft
er.state)} at (${expectation.at}) is [${expectation.is}]`); |
| 157 } | 211 } |
| 158 }, | 212 }, |
| 213 }); |
| 159 }); | 214 }); |
| 160 }); | 215 }); |
| 216 |
| 217 for (var stateTransitionTest of stateTransitionTests) { |
| 218 stateTransitionTest.applyStateTransition(); |
| 219 } |
| 220 |
| 221 requestAnimationFrame(function() { |
| 222 for (var stateTransitionTest of stateTransitionTests) { |
| 223 stateTransitionTest.assert(); |
| 224 } |
| 225 resolve(); |
| 226 }); |
| 161 }); | 227 }); |
| 162 | |
| 163 // Separate style modification from measurement as different phases to avoid a
style recalc storm. | |
| 164 for (var stateTransitionTest of stateTransitionTests) { | |
| 165 stateTransitionTest.applyStateTransition(); | |
| 166 } | |
| 167 for (var stateTransitionTest of stateTransitionTests) { | |
| 168 stateTransitionTest.assert(); | |
| 169 } | |
| 170 } | 228 } |
| 171 | 229 |
| 172 function loadScript(url) { | 230 function loadScript(url) { |
| 173 return new Promise(function(resolve) { | 231 return new Promise(function(resolve) { |
| 174 var script = document.createElement('script'); | 232 var script = document.createElement('script'); |
| 175 script.src = url; | 233 script.src = url; |
| 176 script.onload = resolve; | 234 script.onload = resolve; |
| 177 document.head.appendChild(script); | 235 document.head.appendChild(script); |
| 178 }); | 236 }); |
| 179 } | 237 } |
| 180 | 238 |
| 181 loadScript('../../resources/testharness.js').then(function() { | 239 loadScript('../../resources/testharness.js').then(function() { |
| 182 return loadScript('../../resources/testharnessreport.js'); | 240 return loadScript('../../resources/testharnessreport.js'); |
| 183 }).then(function() { | 241 }).then(function() { |
| 184 var asyncHandle = async_test('This test uses responsive-test.js.') | 242 var asyncHandle = async_test('This test uses responsive-test.js.') |
| 185 requestAnimationFrame(function() { | 243 runPendingResponsiveTests().then(function() { |
| 186 runPendingResponsiveTests(); | 244 asyncHandle.done(); |
| 187 asyncHandle.done() | |
| 188 }); | 245 }); |
| 189 }); | 246 }); |
| 190 | 247 |
| 191 | 248 |
| 192 window.assertResponsive = assertResponsive; | 249 window.assertCSSResponsive = assertCSSResponsive; |
| 250 window.assertSVGResponsive = assertSVGResponsive; |
| 193 | 251 |
| 194 })(); | 252 })(); |
| OLD | NEW |