Chromium Code Reviews| Index: LayoutTests/animations/responsive/resources/responsive-test.js |
| diff --git a/LayoutTests/animations/responsive/resources/responsive-test.js b/LayoutTests/animations/responsive/resources/responsive-test.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a80a103a980bf3f8bf7d2f3dd218cc8f9dd69e34 |
| --- /dev/null |
| +++ b/LayoutTests/animations/responsive/resources/responsive-test.js |
| @@ -0,0 +1,208 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +/* |
| +Exported function: |
| +assertResponsive |
| + |
| +Call signature: |
| +assertResponsive({ |
| + property: <CSS Property>, |
| + from: ?<CSS Value>, |
| + to: ?<CSS Value>, |
| + configurations: [{ |
| + state: { |
| + ?targetStyle: { <CSS Property>: <CSS Value> }, |
| + ?parentStyle: { <CSS Property>: <CSS Value> }, |
| + }, |
| + expect: [ |
| + { at: <Float>, is: <CSS Value> } |
| + ], |
| + }], |
| +}) |
| + |
| +Description: |
| +assertResponsive takes a property specific interpolation and a list of style |
| +configurations with interpolation expectations that apply to each |
| +configuration. |
| +It starts the interpolation in every configuration, changes the |
| +state to every other configuration (n * (n - 1) complexity) and asserts that |
| +each destination configuration's expectations are met. |
| +This test is designed to catch stale interpolation caches. |
| +*/ |
| + |
| +(function() { |
| +'use strict'; |
| +var sharedStyle = null; |
| +var animationCount = 0; |
| +var pendingResponsiveTests = []; |
| + |
| +function assertResponsive(options) { |
| + pendingResponsiveTests.push(options); |
| +} |
| + |
| +function createTransitions(configurations) { |
| + var transitions = []; |
| + for (var i = 0; i < configurations.length; i++) { |
| + var beforeConfiguration = configurations[i]; |
| + var beforeExpect = beforeConfiguration.expect; |
| + for (var j = i; j < configurations.length; j++) { |
| + if (j == i) { |
| + continue; |
| + } |
|
shans
2015/06/18 06:26:02
for (var j = i + 1; ...
alancutter (OOO until 2018)
2015/06/18 07:11:32
"var j = i" was a debugging tweak that got acciden
|
| + var afterConfiguration = configurations[j]; |
| + var afterExpect = afterConfiguration.expect; |
| + |
| + console.assert(beforeExpect.length == afterExpect.length); |
| + for (var k = 0; k < beforeExpect.length; k++) { |
| + console.assert(beforeExpect[k].at == afterExpect[k].at); |
|
shans
2015/06/18 06:26:02
It's really not very obvious from the input data s
alancutter (OOO until 2018)
2015/06/18 07:11:32
On second thought there's no reason to require thi
|
| + } |
| + |
| + transitions.push({ |
| + before: beforeConfiguration, |
| + after: afterConfiguration, |
| + }); |
| + } |
| + } |
| + return transitions; |
| +} |
| + |
| +function createElement(tag, container) { |
| + var element = document.createElement(tag); |
| + if (container) { |
| + container.appendChild(element); |
| + } |
| + return element; |
| +} |
| + |
| +function createTargets(n, container) { |
| + var targets = []; |
| + for (var i = 0; i < n; i++) { |
| + targets.push(createElement('div', container)); |
| + } |
| + return targets; |
| +} |
| + |
| +function setInlineStyle(target, style) { |
|
shans
2015/06/18 06:26:02
This seems predicated on specifying style dictiona
alancutter (OOO until 2018)
2015/06/18 07:11:32
I can't think of any tests that would need more th
|
| + for (var property in style) { |
| + target.style[property] = style[property]; |
| + } |
| +} |
| + |
| +function setState(targets, state) { |
| + var parentStyle = state.parentStyle; |
| + if (parentStyle) { |
| + var parent = targets[0].parentElement; |
| + console.assert(targets.every(function(target) { return target.parentElement === parent; })); |
| + setInlineStyle(parent, parentStyle); |
| + } |
| + var targetStyle = state.targetStyle; |
| + if (targetStyle) { |
| + for (var target of targets) { |
| + setInlineStyle(target, targetStyle); |
| + } |
| + } |
| +} |
| + |
| +function createAnimationName() { |
| + return 'anim' + (animationCount++); |
| +} |
| + |
| +function createKeyframes(animationName, property, from, to) { |
| + return ` |
| +@keyframes ${animationName} { |
| + from { ${property}: ${from}; } |
| + to { ${property}: ${to}; } |
| +}`; |
| +} |
| + |
| +function addGlobalStyle(styleText) { |
| + if (!sharedStyle) { |
| + sharedStyle = createElement('style', document.documentElement); |
| + } |
| + sharedStyle.textContent += styleText; |
| +} |
| + |
| +function startPausedAnimations(targets, animationName, fractions) { |
| + console.assert(targets.length == fractions.length); |
| + for (var i = 0; i < targets.length; i++) { |
| + var target = targets[i]; |
| + var fraction = fractions[i]; |
| + console.assert(fraction >= 0 && fraction <= 1); |
| + target.style.animation = `${animationName} 1s linear both paused`; |
| + target.style.animationDelay = `${-fraction}s`; |
| + } |
| +} |
| + |
| +function runPendingResponsiveTests() { |
| + var transitionTests = []; |
| + pendingResponsiveTests.forEach(function(options) { |
| + var property = options.property; |
| + var from = options.from; |
| + var to = options.to; |
| + var animationName = createAnimationName(); |
| + addGlobalStyle(createKeyframes(animationName, property, from, to)); |
| + |
| + var transitions = createTransitions(options.configurations); |
| + transitions.forEach(function(transition) { |
| + var before = transition.before; |
| + var after = transition.after; |
| + var container = createElement('div', document.body); |
| + var targets = createTargets(before.expect.length, container); |
| + |
| + setState(targets, before.state); |
| + startPausedAnimations(targets, animationName, before.expect.map(function(expectation) { return expectation.at; })); |
| + transitionTests.push({ |
| + applyTransition() { |
| + setState(targets, after.state); |
| + }, |
| + assert() { |
| + for (var i = 0; i < targets.length; i++) { |
| + var target = targets[i]; |
| + var expectation = after.expect[i]; |
| + var actual = getComputedStyle(target)[property]; |
| + test(function() { |
| + assert_equals(actual, expectation.is); |
| + }, `Animation on property <${property}> from [${from}] to [${to}] with ${JSON.stringify(before.state)} changed to ${JSON.stringify(after.state)} at (${expectation.at}) is [${expectation.is}]`); |
| + } |
| + }, |
| + }); |
| + }); |
| + }); |
| + |
| + // Force style recalc to instantiate animations internally. |
| + getComputedStyle(document.body).color; |
| + |
| + // Separate style application and measurement into different phases to avoid a style recalc storm. |
| + for (var transitionTest of transitionTests) { |
| + transitionTest.applyTransition(); |
| + } |
| + for (var transitionTest of transitionTests) { |
| + transitionTest.assert(); |
| + } |
|
shans
2015/06/18 06:26:02
why are these separate loops?
alancutter (OOO until 2018)
2015/06/18 07:11:32
The above comment describes. I batch the style mod
|
| +} |
| + |
| +function loadScript(url) { |
| + return new Promise(function(resolve) { |
| + var script = document.createElement('script'); |
| + script.src = url; |
| + script.onload = resolve; |
| + document.head.appendChild(script); |
| + }); |
| +} |
| + |
| +loadScript('../../resources/testharness.js').then(function() { |
| + return loadScript('../../resources/testharnessreport.js'); |
| +}).then(function() { |
| + var asyncHandle = async_test('This test uses responsive-test.js.') |
| + requestAnimationFrame(function() { |
| + runPendingResponsiveTests(); |
| + asyncHandle.done() |
| + }); |
| +}); |
| + |
| + |
| +window.assertResponsive = assertResponsive; |
| + |
| +})(); |