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 |