Chromium Code Reviews| 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, | |
|
dstockwell
2015/11/17 22:13:02
might help to validate that everything necessary i
alancutter (OOO until 2018)
2015/11/17 22:35:09
Added assert.
| |
| 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 return createElement(options.targetTag, targetContainer, 'target', svgNa mespace); | |
| 82 }, | |
| 83 setValue(target, property, value) { | |
| 84 target.setAttribute(property, value); | |
| 85 }, | |
| 86 getAnimatedValue(target, property) { | |
| 87 return options.getter ? options.getter(target) : target[property].animVa l; | |
| 88 }, | |
| 89 }, | |
| 90 }); | |
| 42 } | 91 } |
| 43 | 92 |
| 44 function createStateTransitions(configurations) { | 93 function createStateTransitions(configurations) { |
| 45 var stateTransitions = []; | 94 var stateTransitions = []; |
| 46 for (var i = 0; i < configurations.length; i++) { | 95 for (var i = 0; i < configurations.length; i++) { |
| 47 var beforeConfiguration = configurations[i]; | 96 var beforeConfiguration = configurations[i]; |
| 48 for (var j = 0; j < configurations.length; j++) { | 97 for (var j = 0; j < configurations.length; j++) { |
| 49 var afterConfiguration = configurations[j]; | 98 var afterConfiguration = configurations[j]; |
| 50 if (j != i) { | 99 if (j != i) { |
| 51 stateTransitions.push({ | 100 stateTransitions.push({ |
| 52 before: beforeConfiguration, | 101 before: beforeConfiguration, |
| 53 after: afterConfiguration, | 102 after: afterConfiguration, |
| 54 }); | 103 }); |
| 55 } | 104 } |
| 56 } | 105 } |
| 57 } | 106 } |
| 58 return stateTransitions; | 107 return stateTransitions; |
| 59 } | 108 } |
| 60 | 109 |
| 61 function createElement(tag, container, className) { | 110 function createElement(tag, container, className, namespace) { |
| 62 var element = document.createElement(tag); | 111 var element = document.createElementNS(namespace || htmlNamespace, tag); |
| 63 if (container) { | 112 if (container) { |
| 64 container.appendChild(element); | 113 container.appendChild(element); |
| 65 } | 114 } |
| 66 if (className) { | 115 if (className) { |
| 67 element.classList.add(className); | 116 element.classList.add(className); |
| 68 } | 117 } |
| 69 return element; | 118 return element; |
| 70 } | 119 } |
| 71 | 120 |
| 72 function createTargets(n, container) { | 121 function createTargets(bindings, n, container) { |
| 73 var targets = []; | 122 var targets = []; |
| 74 for (var i = 0; i < n; i++) { | 123 for (var i = 0; i < n; i++) { |
| 75 targets.push(createElement('div', container, 'target')); | 124 targets.push(bindings.createTarget(container)); |
| 76 } | 125 } |
| 77 return targets; | 126 return targets; |
| 78 } | 127 } |
| 79 | 128 |
| 80 function setState(targets, property, state) { | 129 function setState(bindings, targets, property, state) { |
| 81 if (state.inherited) { | 130 if (state.inherited) { |
| 82 var parent = targets[0].parentElement; | 131 var parent = targets[0].parentElement; |
| 83 console.assert(targets.every(function(target) { return target.parentElement === parent; })); | 132 console.assert(targets.every(function(target) { return target.parentElement === parent; })); |
| 84 parent.style[property] = state.inherited; | 133 bindings.setValue(parent, property, state.inherited); |
| 85 } | 134 } |
| 86 if (state.underlying) { | 135 if (state.underlying) { |
| 87 for (var target of targets) { | 136 for (var target of targets) { |
| 88 target.style[property] = state.underlying; | 137 bindings.setValue(target, property, state.underlying); |
| 89 } | 138 } |
| 90 } | 139 } |
| 91 } | 140 } |
| 92 | 141 |
| 93 function keyframeText(options, keyframeName) { | 142 function keyframeText(options, keyframeName) { |
| 94 return (keyframeName in options) ? `[${options[keyframeName]}]` : 'neutral'; | 143 return (keyframeName in options) ? `[${options[keyframeName]}]` : 'neutral'; |
| 95 } | 144 } |
| 96 | 145 |
| 97 function createKeyframes(options) { | 146 function createKeyframes(prefixedProperty, options) { |
| 98 var keyframes = []; | 147 var keyframes = []; |
| 99 if ('from' in options) { | 148 if ('from' in options) { |
| 100 keyframes.push({ | 149 keyframes.push({ |
| 101 offset: 0, | 150 offset: 0, |
| 102 [options.property]: options.from, | 151 [prefixedProperty]: options.from, |
| 103 }); | 152 }); |
| 104 } | 153 } |
| 105 if ('to' in options) { | 154 if ('to' in options) { |
| 106 keyframes.push({ | 155 keyframes.push({ |
| 107 offset: 1, | 156 offset: 1, |
| 108 [options.property]: options.to, | 157 [prefixedProperty]: options.to, |
| 109 }); | 158 }); |
| 110 } | 159 } |
| 111 return keyframes; | 160 return keyframes; |
| 112 } | 161 } |
| 113 | 162 |
| 114 function startPausedAnimations(targets, keyframes, fractions) { | 163 function startPausedAnimations(targets, keyframes, fractions) { |
| 115 console.assert(targets.length == fractions.length); | 164 console.assert(targets.length == fractions.length); |
| 116 for (var i = 0; i < targets.length; i++) { | 165 for (var i = 0; i < targets.length; i++) { |
| 117 var target = targets[i]; | 166 var target = targets[i]; |
| 118 var fraction = fractions[i]; | 167 var fraction = fractions[i]; |
| 119 console.assert(fraction >= 0 && fraction < 1); | 168 console.assert(fraction >= 0 && fraction < 1); |
| 120 var animation = target.animate(keyframes, 1); | 169 var animation = target.animate(keyframes, 1); |
| 121 animation.currentTime = fraction; | 170 animation.currentTime = fraction; |
| 122 animation.pause(); | 171 animation.pause(); |
| 123 } | 172 } |
| 124 } | 173 } |
| 125 | 174 |
| 126 function runPendingResponsiveTests() { | 175 function runPendingResponsiveTests() { |
| 127 var stateTransitionTests = []; | 176 return new Promise(function(resolve) { |
| 128 pendingResponsiveTests.forEach(function(options) { | 177 var stateTransitionTests = []; |
| 129 var property = options.property; | 178 pendingResponsiveTests.forEach(function(responsiveTest) { |
| 130 var from = options.from; | 179 var options = responsiveTest.options; |
| 131 var to = options.to; | 180 var bindings = responsiveTest.bindings; |
| 132 var keyframes = createKeyframes(options); | 181 var property = options.property; |
| 133 var fromText = keyframeText(options, 'from'); | 182 var prefixedProperty = bindings.prefixProperty(property); |
| 134 var toText = keyframeText(options, 'to'); | 183 var from = options.from; |
| 184 var to = options.to; | |
| 185 var keyframes = createKeyframes(prefixedProperty, options); | |
| 186 var fromText = keyframeText(options, 'from'); | |
| 187 var toText = keyframeText(options, 'to'); | |
| 135 | 188 |
| 136 var stateTransitions = createStateTransitions(options.configurations); | 189 var stateTransitions = createStateTransitions(options.configurations); |
| 137 stateTransitions.forEach(function(stateTransition) { | 190 stateTransitions.forEach(function(stateTransition) { |
| 138 var before = stateTransition.before; | 191 var before = stateTransition.before; |
| 139 var after = stateTransition.after; | 192 var after = stateTransition.after; |
| 140 var container = createElement('div', document.body); | 193 var container = bindings.createTargetContainer(document.body); |
| 141 var targets = createTargets(after.expect.length, container); | 194 var targets = createTargets(bindings, after.expect.length, container); |
| 142 | 195 |
| 143 setState(targets, property, before.state); | 196 setState(bindings, targets, property, before.state); |
| 144 startPausedAnimations(targets, keyframes, after.expect.map(function(expect ation) { return expectation.at; })); | 197 startPausedAnimations(targets, keyframes, after.expect.map(function(expe ctation) { return expectation.at; })); |
| 145 stateTransitionTests.push({ | 198 stateTransitionTests.push({ |
| 146 applyStateTransition() { | 199 applyStateTransition() { |
| 147 setState(targets, property, after.state); | 200 setState(bindings, targets, property, after.state); |
| 148 }, | 201 }, |
| 149 assert() { | 202 assert() { |
| 150 for (var i = 0; i < targets.length; i++) { | 203 for (var i = 0; i < targets.length; i++) { |
| 151 var target = targets[i]; | 204 var target = targets[i]; |
| 152 var expectation = after.expect[i]; | 205 var expectation = after.expect[i]; |
| 153 var actual = getComputedStyle(target)[property]; | 206 var actual = bindings.getAnimatedValue(target, property); |
| 154 test(function() { | 207 test(function() { |
| 155 assert_equals(actual, expectation.is); | 208 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}]`); | 209 }, `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 } | 210 } |
| 158 }, | 211 }, |
| 212 }); | |
| 159 }); | 213 }); |
| 160 }); | 214 }); |
| 215 | |
| 216 for (var stateTransitionTest of stateTransitionTests) { | |
| 217 stateTransitionTest.applyStateTransition(); | |
| 218 } | |
| 219 | |
| 220 requestAnimationFrame(function() { | |
| 221 for (var stateTransitionTest of stateTransitionTests) { | |
| 222 stateTransitionTest.assert(); | |
| 223 } | |
| 224 resolve(); | |
| 225 }); | |
| 161 }); | 226 }); |
| 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 } | 227 } |
| 171 | 228 |
| 172 function loadScript(url) { | 229 function loadScript(url) { |
| 173 return new Promise(function(resolve) { | 230 return new Promise(function(resolve) { |
| 174 var script = document.createElement('script'); | 231 var script = document.createElement('script'); |
| 175 script.src = url; | 232 script.src = url; |
| 176 script.onload = resolve; | 233 script.onload = resolve; |
| 177 document.head.appendChild(script); | 234 document.head.appendChild(script); |
| 178 }); | 235 }); |
| 179 } | 236 } |
| 180 | 237 |
| 181 loadScript('../../resources/testharness.js').then(function() { | 238 loadScript('../../resources/testharness.js').then(function() { |
| 182 return loadScript('../../resources/testharnessreport.js'); | 239 return loadScript('../../resources/testharnessreport.js'); |
| 183 }).then(function() { | 240 }).then(function() { |
| 184 var asyncHandle = async_test('This test uses responsive-test.js.') | 241 var asyncHandle = async_test('This test uses responsive-test.js.') |
| 185 requestAnimationFrame(function() { | 242 runPendingResponsiveTests().then(function() { |
| 186 runPendingResponsiveTests(); | 243 asyncHandle.done(); |
| 187 asyncHandle.done() | |
| 188 }); | 244 }); |
| 189 }); | 245 }); |
| 190 | 246 |
| 191 | 247 |
| 192 window.assertResponsive = assertResponsive; | 248 window.assertCSSResponsive = assertCSSResponsive; |
| 249 window.assertSVGResponsive = assertSVGResponsive; | |
| 193 | 250 |
| 194 })(); | 251 })(); |
| OLD | NEW |