diff options
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces/KeyframeEffect')
9 files changed, 2492 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html new file mode 100644 index 000000000..97c7613f8 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/constructor.html @@ -0,0 +1,278 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly constructor tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#the-keyframeeffect-interfaces"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/keyframe-utils.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<style> +#target { + border-style: solid; /* so border-*-width values don't compute to 0 */ +} +</style> +<script> +"use strict"; + +var target = document.getElementById("target"); + +test(function(t) { + gEmptyKeyframeListTests.forEach(function(frames) { + assert_equals(new KeyframeEffectReadOnly(target, frames) + .getKeyframes().length, + 0, "number of frames for " + JSON.stringify(frames)); + }); +}, "a KeyframeEffectReadOnly can be constructed with no frames"); + +test(function(t) { + gEasingValueTests.forEach(function(subtest) { + var easing = subtest[0]; + var expected = subtest[1]; + var effect = new KeyframeEffectReadOnly(target, { + left: ["10px", "20px"], + easing: easing + }); + assert_equals(effect.getKeyframes()[0].easing, expected, + "resulting easing for '" + easing + "'"); + }); +}, "easing values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in a property-indexed keyframe"); + +test(function(t) { + gEasingValueTests.forEach(function(subtest) { + var easing = subtest[0]; + var expected = subtest[1]; + var effect = new KeyframeEffectReadOnly(target, [ + { offset: 0, left: "10px", easing: easing }, + { offset: 1, left: "20px" } + ]); + assert_equals(effect.getKeyframes()[0].easing, expected, + "resulting easing for '" + easing + "'"); + }); +}, "easing values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in regular keyframes"); + +test(function(t) { + gEasingValueTests.forEach(function(subtest) { + var easing = subtest[0]; + var expected = subtest[1]; + var effect = new KeyframeEffectReadOnly(target, { + left: ["10px", "20px"] + }, { easing: easing }); + assert_equals(effect.timing.easing, expected, + "resulting easing for '" + easing + "'"); + }); +}, "easing values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in KeyframeTimingOptions"); + +test(function(t) { + var getKeyframe = function(composite) { + return { left: [ "10px", "20px" ], composite: composite }; + }; + gGoodKeyframeCompositeValueTests.forEach(function(composite) { + var effect = new KeyframeEffectReadOnly(target, getKeyframe(composite)); + assert_equals(effect.getKeyframes()[0].composite, composite, + "resulting composite for '" + composite + "'"); + }); + gBadCompositeValueTests.forEach(function(composite) { + assert_throws(new TypeError, function() { + new KeyframeEffectReadOnly(target, getKeyframe(composite)); + }); + }); +}, "composite values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in property-indexed keyframes"); + +test(function(t) { + var getKeyframes = function(composite) { + return [ + { offset: 0, left: "10px", composite: composite }, + { offset: 1, left: "20px" } + ]; + }; + gGoodKeyframeCompositeValueTests.forEach(function(composite) { + var effect = new KeyframeEffectReadOnly(target, getKeyframes(composite)); + assert_equals(effect.getKeyframes()[0].composite, composite, + "resulting composite for '" + composite + "'"); + }); + gBadCompositeValueTests.forEach(function(composite) { + assert_throws(new TypeError, function() { + new KeyframeEffectReadOnly(target, getKeyframes(composite)); + }); + }); +}, "composite values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in regular keyframes"); + +test(function(t) { + gGoodOptionsCompositeValueTests.forEach(function(composite) { + var effect = new KeyframeEffectReadOnly(target, { + left: ["10px", "20px"] + }, { composite: composite }); + assert_equals(effect.getKeyframes()[0].composite, composite, + "resulting composite for '" + composite + "'"); + }); + gBadCompositeValueTests.forEach(function(composite) { + assert_throws(new TypeError, function() { + new KeyframeEffectReadOnly(target, { + left: ["10px", "20px"] + }, { composite: composite }); + }); + }); +}, "composite values are parsed correctly when passed to the " + + "KeyframeEffectReadOnly constructor in KeyframeTimingOptions"); + +gPropertyIndexedKeyframesTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), subtest.output); + }, "a KeyframeEffectReadOnly can be constructed with " + subtest.desc); + + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, subtest.input); + var secondEffect = + new KeyframeEffectReadOnly(target, effect.getKeyframes()); + assert_frame_lists_equal(secondEffect.getKeyframes(), + effect.getKeyframes()); + }, "a KeyframeEffectReadOnly constructed with " + subtest.desc + + " roundtrips"); +}); + +test(function(t) { + var expectedOrder = ["composite", "easing", "offset", "left", "marginLeft"]; + var actualOrder = []; + var kf1 = {}; + var kf2 = { marginLeft: "10px", left: "20px", offset: 1 }; + [{ p: "marginLeft", v: "10px" }, + { p: "left", v: "20px" }, + { p: "offset", v: "0" }, + { p: "easing", v: "linear" }, + { p: "composite", v: "replace" }].forEach(function(e) { + Object.defineProperty(kf1, e.p, { + enumerable: true, + get: function() { actualOrder.push(e.p); return e.v; } + }); + }); + new KeyframeEffectReadOnly(target, [kf1, kf2]); + assert_array_equals(actualOrder, expectedOrder, "property access order"); +}, "the KeyframeEffectReadOnly constructor reads keyframe properties in the " + + "expected order"); + +gKeyframeSequenceTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), subtest.output); + }, "a KeyframeEffectReadOnly can be constructed with " + subtest.desc); + + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, subtest.input); + var secondEffect = + new KeyframeEffectReadOnly(target, effect.getKeyframes()); + assert_frame_lists_equal(secondEffect.getKeyframes(), + effect.getKeyframes()); + }, "a KeyframeEffectReadOnly constructed with " + subtest.desc + + " roundtrips"); +}); + +gInvalidKeyframesTests.forEach(function(subtest) { + test(function(t) { + assert_throws(subtest.expected, function() { + new KeyframeEffectReadOnly(target, subtest.input); + }); + }, "KeyframeEffectReadOnly constructor throws with " + subtest.desc); +}); + +gInvalidEasingInKeyframeSequenceTests.forEach(function(subtest) { + test(function(t) { + assert_throws(new TypeError, function() { + new KeyframeEffectReadOnly(target, subtest.input); + }); + }, "Invalid easing [" + subtest.desc + "] in keyframe sequence " + + "should be thrown"); +}); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }); + + var timing = effect.timing; + assert_equals(timing.delay, 0, "default delay"); + assert_equals(timing.endDelay, 0, "default endDelay"); + assert_equals(timing.fill, "auto", "default fill"); + assert_equals(timing.iterations, 1.0, "default iterations"); + assert_equals(timing.iterationStart, 0.0, "default iterationStart"); + assert_equals(timing.duration, "auto", "default duration"); + assert_equals(timing.direction, "normal", "default direction"); + assert_equals(timing.easing, "linear", "default easing"); + + assert_equals(effect.composite, "replace", "default composite"); + assert_equals(effect.iterationComposite, "replace", + "default iterationComposite"); + assert_equals(effect.spacing, "distribute", + "default spacing"); +}, "a KeyframeEffectReadOnly constructed without any " + + "KeyframeEffectOptions object"); + +gKeyframeEffectOptionTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + // Helper function to provide default expected values when the test does + // not supply them. + var expected = function(field, defaultValue) { + return field in stest.expected ? stest.expected[field] : defaultValue; + }; + + var timing = effect.timing; + assert_equals(timing.delay, expected("delay", 0), + "timing delay"); + assert_equals(timing.fill, expected("fill", "auto"), + "timing fill"); + assert_equals(timing.iterations, expected("iterations", 1), + "timing iterations"); + assert_equals(timing.duration, expected("duration", "auto"), + "timing duration"); + assert_equals(timing.direction, expected("direction", "normal"), + "timing direction"); + + }, "a KeyframeEffectReadOnly constructed by " + stest.desc); +}); + +gInvalidKeyframeEffectOptionTests.forEach(function(stest) { + test(function(t) { + assert_throws(stest.expected, function() { + new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + }); + }, "Invalid KeyframeEffectReadOnly option by " + stest.desc); +}); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, + { left: ["10px", "20px"] }, + { duration: 100 * MS_PER_SEC, + fill: "forwards" }); + assert_equals(effect.target, null, + "Effect created with null target has correct target"); +}, "a KeyframeEffectReadOnly constructed with null target"); + +test(function(t) { + var effect = new KeyframeEffect(target, null); + assert_class_string(effect, "KeyframeEffect"); + assert_class_string(effect.timing, "AnimationEffectTiming"); +}, "KeyframeEffect constructor creates an AnimationEffectTiming timing object"); + +test(function(t) { + var test_error = { name: "test" }; + + assert_throws(test_error, function() { + new KeyframeEffect(target, { get left() { throw test_error }}) + }); +}, "KeyframeEffect constructor propagates exceptions generated by accessing" + + " the options object"); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-contructor.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-contructor.html new file mode 100644 index 000000000..bc278389c --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/copy-contructor.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffect copy constructor tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-keyframeeffect-keyframeeffect-source"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var effect = new KeyframeEffectReadOnly(createDiv(t), null); + assert_equals(effect.constructor.name, 'KeyframeEffectReadOnly'); + assert_equals(effect.timing.constructor.name, + 'AnimationEffectTimingReadOnly'); + + // Make a mutable copy + var copiedEffect = new KeyframeEffect(effect); + assert_equals(copiedEffect.constructor.name, 'KeyframeEffect'); + assert_equals(copiedEffect.timing.constructor.name, 'AnimationEffectTiming'); +}, 'Test mutable copy from a KeyframeEffectReadOnly source'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html new file mode 100644 index 000000000..05019cdf3 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/effect-easing.html @@ -0,0 +1,683 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Effect-level easing tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#calculating-the-transformed-time"> +<link rel="author" title="Hiroyuki Ikezoe" href="mailto:hiikezoe@mozilla-japan.org"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/effect-easing-tests.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +"use strict"; + +function assert_style_left_at(animation, time, easingFunction) { + animation.currentTime = time; + var portion = time / animation.effect.timing.duration; + assert_approx_equals(pxToNum(getComputedStyle(animation.effect.target).left), + easingFunction(portion) * 100, + 0.01, + 'The left of the animation should be approximately ' + + easingFunction(portion) * 100 + ' at ' + time + 'ms'); +} + +gEffectEasingTests.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px' }, { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: options.easing }); + var easing = options.easingFunction; + + anim.pause(); + + assert_style_left_at(anim, 0, easing); + assert_style_left_at(anim, 250, easing); + assert_style_left_at(anim, 500, easing); + assert_style_left_at(anim, 750, easing); + assert_style_left_at(anim, 1000, easing); + }, options.desc); +}); + +var gEffectEasingTestsWithKeyframeEasing = [ + { + desc: 'effect easing produces values greater than 1 with keyframe ' + + 'easing cubic-bezier(0, 0, 0, 0)', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)', + keyframeEasing: 'cubic-bezier(0, 0, 0, 0)', // linear + easingFunction: cubicBezier(0, 1.5, 1, 1.5) + }, + { + desc: 'effect easing produces values greater than 1 with keyframe ' + + 'easing cubic-bezier(1, 1, 1, 1)', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)', + keyframeEasing: 'cubic-bezier(1, 1, 1, 1)', // linear + easingFunction: cubicBezier(0, 1.5, 1, 1.5) + }, + { + desc: 'effect easing produces negative values 1 with keyframe ' + + 'easing cubic-bezier(0, 0, 0, 0)', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)', + keyframeEasing: 'cubic-bezier(0, 0, 0, 0)', // linear + easingFunction: cubicBezier(0, -0.5, 1, -0.5) + }, + { + desc: 'effect easing produces negative values 1 with keyframe ' + + 'easing cubic-bezier(1, 1, 1, 1)', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)', + keyframeEasing: 'cubic-bezier(1, 1, 1, 1)', // linear + easingFunction: cubicBezier(0, -0.5, 1, -0.5) + }, +]; + +gEffectEasingTestsWithKeyframeEasing.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + [ { left: '0px', easing: options.keyframeEasing }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: options.easing }); + var easing = options.easingFunction; + + anim.pause(); + + assert_style_left_at(anim, 0, easing); + assert_style_left_at(anim, 250, easing); + assert_style_left_at(anim, 500, easing); + assert_style_left_at(anim, 750, easing); + assert_style_left_at(anim, 1000, easing); + }, options.desc); +}); + +// Other test cases that effect easing produces values outside of [0,1]. +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-start' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + anim.pause(); + + // The bezier function produces values greater than 1 in (0.23368794, 1) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 230; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 250; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces values greater than 1 with step-start keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-end' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + anim.pause(); + + // The bezier function produces values greater than 1 in (0.23368794, 1) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 230; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 250; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces values greater than 1 with step-end keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-start' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + anim.pause(); + + // The bezier function produces negative values in (0, 0.766312060) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 750; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 800; + assert_equals(getComputedStyle(target).left, '100px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces negative values with step-start keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate([ { left: '0px', easing: 'step-end' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + anim.pause(); + + // The bezier function produces negative values in (0, 0.766312060) + anim.currentTime = 0; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 750; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 800; + assert_equals(getComputedStyle(target).left, '0px'); + anim.currentTime = 1000; + assert_equals(getComputedStyle(target).left, '100px'); +}, 'effect easing produces negative values with step-end keyframe'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#.5,1,.5,0 + [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + var keyframeEasing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0.5, 1, 0.5, 0)(x); + } + var keyframeEasingExtrapolated = function(x) { + assert_greater_than(x, 1.0, + 'This function should be called in (1.0, infinity) range'); + // p3x + (p2y - p3y) / (p2x - p3x) * (x - p3x) + return 1.0 + (0 - 1) / (0.5 - 1) * (x - 1.0); + } + var effectEasing = function(x) { + return cubicBezier(0, 1.5, 1, 1.5)(x); + } + + anim.pause(); + + // The effect-easing produces values greater than 1 in (0.23368794, 1) + assert_style_left_at(anim, 0, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 230, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 240, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 700, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 990, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 1000, function(x) { + return keyframeEasing(effectEasing(x)); + }); +}, 'effect easing produces values greater than 1 with keyframe easing ' + + 'producing values greater than 1'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#0,1.5,1,1.5 + [ { left: '0px', easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, 1.5, 1, 1.5)' }); + var easing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0, 1.5, 1, 1.5)(x); + } + var easingExtrapolated = function(x) { + assert_greater_than(x, 1.0, + 'This function should be called in negative range'); + // For cubic-bezier(0, 1.5, 1, 1.5), the tangent at the + // endpoint (x = 1.0) is infinity so we should just return 1.0. + return 1.0; + } + + anim.pause(); + + // The effect-easing produces values greater than 1 in (0.23368794, 1) + assert_style_left_at(anim, 0, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 230, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 240, function(x) { + return easingExtrapolated(easing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 700, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 990, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 1000, function(x) { + return easing(easing(x)) + }); +}, 'effect easing which produces values greater than 1 and the tangent on ' + + 'the upper boundary is infinity with keyframe easing producing values ' + + 'greater than 1'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#.5,1,.5,0 + [ { left: '0px', easing: 'cubic-bezier(0.5, 1, 0.5, 0)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + var keyframeEasing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0.5, 1, 0.5, 0)(x); + } + var keyframeEasingExtrapolated = function(x) { + assert_less_than(x, 0.0, + 'This function should be called in negative range'); + // p0x + (p1y - p0y) / (p1x - p0x) * (x - p0x) + return (1 / 0.5) * x; + } + var effectEasing = function(x) { + return cubicBezier(0, -0.5, 1, -0.5)(x); + } + + anim.pause(); + + // The effect-easing produces negative values in (0, 0.766312060) + assert_style_left_at(anim, 0, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 10, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 300, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 750, function(x) { + return keyframeEasingExtrapolated(effectEasing(x)); + }); + assert_style_left_at(anim, 770, function(x) { + return keyframeEasing(effectEasing(x)); + }); + assert_style_left_at(anim, 1000, function(x) { + return keyframeEasing(effectEasing(x)); + }); +}, 'effect easing produces negative values with keyframe easing ' + + 'producing negative values'); + +test(function(t) { + var target = createDiv(t); + target.style.position = 'absolute'; + var anim = target.animate( + // http://cubic-bezier.com/#0,-0.5,1,-0.5 + [ { left: '0px', easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }, + { left: '100px' } ], + { duration: 1000, + fill: 'forwards', + easing: 'cubic-bezier(0, -0.5, 1, -0.5)' }); + var easing = function(x) { + assert_greater_than_equal(x, 0.0, + 'This function should be called in [0, 1.0] range'); + assert_less_than_equal(x, 1.0, + 'This function should be called in [0, 1.0] range'); + return cubicBezier(0, -0.5, 1, -0.5)(x); + } + var easingExtrapolated = function(x) { + assert_less_than(x, 0.0, + 'This function should be called in negative range'); + // For cubic-bezier(0, -0.5, 1, -0.5), the tangent at the + // endpoint (x = 0.0) is infinity so we should just return 0.0. + return 0.0; + } + + anim.pause(); + + // The effect-easing produces negative values in (0, 0.766312060) + assert_style_left_at(anim, 0, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 10, function(x) { + return easingExtrapolated(easing(x)); + }); + // Near the extreme point of the effect-easing function + assert_style_left_at(anim, 300, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 750, function(x) { + return easingExtrapolated(easing(x)); + }); + assert_style_left_at(anim, 770, function(x) { + return easing(easing(x)) + }); + assert_style_left_at(anim, 1000, function(x) { + return easing(easing(x)) + }); +}, 'effect easing which produces negative values and the tangent on ' + + 'the lower boundary is infinity with keyframe easing producing ' + + 'negative values'); + +var gStepTimingFunctionTests = [ + { + description: 'Test bounds point of step-start easing', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0 }, + { currentTime: 999, progress: 0 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 2000, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing with compositor', + keyframe: [ { opacity: 0 }, + { opacity: 1 } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0 }, + { currentTime: 999, progress: 0 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 2000, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing with reverse direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + direction: 'reverse', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1001, progress: 1 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 1501, progress: 0.5 }, + { currentTime: 2000, progress: 0 }, + { currentTime: 2500, progress: 0 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with iterationStart not at a transition point', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.25, + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0.5 }, + { currentTime: 999, progress: 0.5 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1249, progress: 0.5 }, + { currentTime: 1250, progress: 1 }, + { currentTime: 1749, progress: 1 }, + { currentTime: 1750, progress: 0.5 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 }, + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with iterationStart and delay', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.5, + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 0.5 }, + { currentTime: 999, progress: 0.5 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1499, progress: 1 }, + { currentTime: 1500, progress: 0.5 }, + { currentTime: 2000, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with iterationStart and reverse direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.5, + direction: 'reverse', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1001, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 1 }, + { currentTime: 1999, progress: 1 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step(4, start) easing ' + + 'with iterationStart 0.75 and delay', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterationStart: 0.75, + easing: 'steps(4, start)' + }, + conditions: [ + { currentTime: 0, progress: 0.75 }, + { currentTime: 999, progress: 0.75 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 2000, progress: 1 }, + { currentTime: 2500, progress: 1 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with alternate direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterations: 2, + iterationStart: 1.5, + direction: 'alternate', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1001, progress: 0.5 }, + { currentTime: 2999, progress: 1 }, + { currentTime: 3000, progress: 0.5 }, + { currentTime: 3500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step-start easing ' + + 'with alternate-reverse direction', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterations: 2, + iterationStart: 0.5, + direction: 'alternate-reverse', + easing: 'steps(2, start)' + }, + conditions: [ + { currentTime: 0, progress: 1 }, + { currentTime: 1000, progress: 1 }, + { currentTime: 1001, progress: 0.5 }, + { currentTime: 2999, progress: 1 }, + { currentTime: 3000, progress: 0.5 }, + { currentTime: 3500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step-start easing in keyframe', + keyframe: [ { width: '0px', easing: 'steps(2, start)' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + }, + conditions: [ + { currentTime: 0, progress: 0, width: '0px' }, + { currentTime: 999, progress: 0, width: '0px' }, + { currentTime: 1000, progress: 0, width: '50px' }, + { currentTime: 1499, progress: 0.499, width: '50px' }, + { currentTime: 1500, progress: 0.5, width: '100px' }, + { currentTime: 2000, progress: 1, width: '100px' }, + { currentTime: 2500, progress: 1, width: '100px' } + ] + }, + { + description: 'Test bounds point of step-end easing ' + + 'with iterationStart and delay', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + duration: 1000, + fill: 'both', + delay: 1000, + iterationStart: 0.5, + easing: 'steps(2, end)' + }, + conditions: [ + { currentTime: 0, progress: 0 }, + { currentTime: 999, progress: 0 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1499, progress: 0.5 }, + { currentTime: 1500, progress: 0 }, + { currentTime: 1999, progress: 0 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 } + ] + }, + { + description: 'Test bounds point of step-end easing ' + + 'with iterationStart not at a transition point', + keyframe: [ { width: '0px' }, + { width: '100px' } ], + effect: { + delay: 1000, + duration: 1000, + fill: 'both', + iterationStart: 0.75, + easing: 'steps(2, end)' + }, + conditions: [ + { currentTime: 0, progress: 0.5 }, + { currentTime: 999, progress: 0.5 }, + { currentTime: 1000, progress: 0.5 }, + { currentTime: 1249, progress: 0.5 }, + { currentTime: 1250, progress: 0 }, + { currentTime: 1749, progress: 0 }, + { currentTime: 1750, progress: 0.5 }, + { currentTime: 2000, progress: 0.5 }, + { currentTime: 2500, progress: 0.5 }, + ] + } +]; + +gStepTimingFunctionTests.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + var animation = target.animate(options.keyframe, options.effect); + options.conditions.forEach(function(condition) { + animation.currentTime = condition.currentTime; + if (typeof condition.progress !== 'undefined') { + assert_equals(animation.effect.getComputedTiming().progress, + condition.progress, + 'Progress at ' + animation.currentTime + 'ms'); + } + if (typeof condition.width !== 'undefined') { + assert_equals(getComputedStyle(target).width, + condition.width, + 'Progress at ' + animation.currentTime + 'ms'); + } + }); + }, options.description); +}); + +gInvalidEasingTests.forEach(function(options) { + test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, + function() { + div.animate({ easing: options.easing }, 100 * MS_PER_SEC); + }); + }, 'Invalid keyframe easing value: \'' + options.easing + '\''); +}); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html new file mode 100644 index 000000000..4d799272b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/getComputedTiming.html @@ -0,0 +1,212 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly getComputedTiming() tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffectreadonly-getcomputedtiming"> +<link rel="author" title="Boris Chiou" href="mailto:boris.chiou@gmail.com"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +"use strict"; + +var target = document.getElementById("target"); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }); + + var ct = effect.getComputedTiming(); + assert_equals(ct.delay, 0, "computed delay"); + assert_equals(ct.fill, "none", "computed fill"); + assert_equals(ct.iterations, 1.0, "computed iterations"); + assert_equals(ct.duration, 0, "computed duration"); + assert_equals(ct.direction, "normal", "computed direction"); +}, "values of getComputedTiming() when a KeyframeEffectReadOnly is " + + "constructed without any KeyframeEffectOptions object"); + +var gGetComputedTimingTests = [ + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: { } }, + { desc: "a normal KeyframeEffectOptions object", + input: { delay: 1000, + fill: "auto", + iterations: 5.5, + duration: "auto", + direction: "alternate" }, + expected: { delay: 1000, + fill: "none", + iterations: 5.5, + duration: 0, + direction: "alternate" } }, + { desc: "a double value", + input: 3000, + timing: { duration: 3000 }, + expected: { delay: 0, + fill: "none", + iterations: 1, + duration: 3000, + direction: "normal" } }, + { desc: "+Infinity", + input: Infinity, + expected: { duration: Infinity } }, + { desc: "an Infinity duration", + input: { duration: Infinity }, + expected: { duration: Infinity } }, + { desc: "an auto duration", + input: { duration: "auto" }, + expected: { duration: 0 } }, + { desc: "an Infinity iterations", + input: { iterations: Infinity }, + expected: { iterations: Infinity } }, + { desc: "an auto fill", + input: { fill: "auto" }, + expected: { fill: "none" } }, + { desc: "a forwards fill", + input: { fill: "forwards" }, + expected: { fill: "forwards" } } +]; + +gGetComputedTimingTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + // Helper function to provide default expected values when the test does + // not supply them. + var expected = function(field, defaultValue) { + return field in stest.expected ? stest.expected[field] : defaultValue; + }; + + var ct = effect.getComputedTiming(); + assert_equals(ct.delay, expected("delay", 0), + "computed delay"); + assert_equals(ct.fill, expected("fill", "none"), + "computed fill"); + assert_equals(ct.iterations, expected("iterations", 1), + "computed iterations"); + assert_equals(ct.duration, expected("duration", 0), + "computed duration"); + assert_equals(ct.direction, expected("direction", "normal"), + "computed direction"); + + }, "values of getComputedTiming() when a KeyframeEffectReadOnly is " + + "constructed by " + stest.desc); +}); + +var gActiveDurationTests = [ + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: 0 }, + { desc: "a non-zero duration and default iteration count", + input: { duration: 1000 }, + expected: 1000 }, + { desc: "a non-zero duration and integral iteration count", + input: { duration: 1000, iterations: 7 }, + expected: 7000 }, + { desc: "a non-zero duration and fractional iteration count", + input: { duration: 1000, iterations: 2.5 }, + expected: 2500 }, + { desc: "an non-zero duration and infinite iteration count", + input: { duration: 1000, iterations: Infinity }, + expected: Infinity }, + { desc: "an non-zero duration and zero iteration count", + input: { duration: 1000, iterations: 0 }, + expected: 0 }, + { desc: "a zero duration and default iteration count", + input: { duration: 0 }, + expected: 0 }, + { desc: "a zero duration and fractional iteration count", + input: { duration: 0, iterations: 2.5 }, + expected: 0 }, + { desc: "a zero duration and infinite iteration count", + input: { duration: 0, iterations: Infinity }, + expected: 0 }, + { desc: "a zero duration and zero iteration count", + input: { duration: 0, iterations: 0 }, + expected: 0 }, + { desc: "an infinite duration and default iteration count", + input: { duration: Infinity }, + expected: Infinity }, + { desc: "an infinite duration and zero iteration count", + input: { duration: Infinity, iterations: 0 }, + expected: 0 }, + { desc: "an infinite duration and fractional iteration count", + input: { duration: Infinity, iterations: 2.5 }, + expected: Infinity }, + { desc: "an infinite duration and infinite iteration count", + input: { duration: Infinity, iterations: Infinity }, + expected: Infinity }, +]; + +gActiveDurationTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + assert_equals(effect.getComputedTiming().activeDuration, + stest.expected); + + }, "getComputedTiming().activeDuration for " + stest.desc); +}); + +var gEndTimeTests = [ + { desc: "an empty KeyframeEffectOptions object", + input: { }, + expected: 0 }, + { desc: "a non-zero duration and default iteration count", + input: { duration: 1000 }, + expected: 1000 }, + { desc: "a non-zero duration and non-default iteration count", + input: { duration: 1000, iterations: 2.5 }, + expected: 2500 }, + { desc: "a non-zero duration and non-zero delay", + input: { duration: 1000, delay: 1500 }, + expected: 2500 }, + { desc: "a non-zero duration, non-zero delay and non-default iteration", + input: { duration: 1000, delay: 1500, iterations: 2 }, + expected: 3500 }, + { desc: "an infinite iteration count", + input: { duration: 1000, iterations: Infinity }, + expected: Infinity }, + { desc: "an infinite duration", + input: { duration: Infinity, iterations: 10 }, + expected: Infinity }, + { desc: "an infinite duration and delay", + input: { duration: Infinity, iterations: 10, delay: 1000 }, + expected: Infinity }, + { desc: "an infinite duration and negative delay", + input: { duration: Infinity, iterations: 10, delay: -1000 }, + expected: Infinity }, + { desc: "an non-zero duration and negative delay", + input: { duration: 1000, iterations: 2, delay: -1000 }, + expected: 1000 }, + { desc: "an non-zero duration and negative delay greater than active " + + "duration", + input: { duration: 1000, iterations: 2, delay: -3000 }, + expected: 0 }, + { desc: "a zero duration and negative delay", + input: { duration: 0, iterations: 2, delay: -1000 }, + expected: 0 } +]; + +gEndTimeTests.forEach(function(stest) { + test(function(t) { + var effect = new KeyframeEffectReadOnly(target, + { left: ["10px", "20px"] }, + stest.input); + + assert_equals(effect.getComputedTiming().endTime, + stest.expected); + + }, "getComputedTiming().endTime for " + stest.desc); +}); + +done(); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html new file mode 100644 index 000000000..743d10887 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/iterationComposite.html @@ -0,0 +1,693 @@ +<!doctype html> +<meta charset=utf-8> +<title>KeyframeEffect.iterationComposite tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#effect-accumulation-section"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="../../testcommon.js"></script> +<div id="log"></div> +<script> +'use strict'; + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '5px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '20px', + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated margin-left style at 50s of the third iteration'); +}, 'iterationComposite of <length> type animation'); + +test(function(t) { + var parent = createDiv(t); + parent.style.width = '100px'; + var div = createDiv(t); + parent.appendChild(div); + + var anim = + div.animate({ width: ['0%', '50%'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '25px', + 'Animated width style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).width, '100px', + 'Animated width style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '125px', + 'Animated width style at 50s of the third iteration'); +}, 'iterationComposite of <percentage> type animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ color: ['rgb(0, 0, 0)', 'rgb(120, 120, 120)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(60, 60, 60)', + 'Animated color style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).color, 'rgb(240, 240, 240)', + 'Animated color style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(255, 255, 255)', + 'Animated color style at 50s of the third iteration'); +}, 'iterationComposite of <color> type animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ color: ['rgb(0, 120, 0)', 'rgb(60, 60, 60)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).color, 'rgb(30, 90, 30)', + 'Animated color style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).color, 'rgb(120, 240, 120)', + 'Animated color style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + // The green color is (240 + 180) / 2 = 210 + assert_equals(getComputedStyle(div).color, 'rgb(150, 210, 150)', + 'Animated color style at 50s of the third iteration'); +}, 'iterationComposite of <color> type animation that green component is ' + + 'decreasing'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ flexGrow: [0, 10] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).flexGrow, '5', + 'Animated flex-grow style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).flexGrow, '20', + 'Animated flex-grow style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).flexGrow, '25', + 'Animated flex-grow style at 50s of the third iteration'); +}, 'iterationComposite of <number> type animation'); + +test(function(t) { + var div = createDiv(t); + div.style.position = 'absolute'; + var anim = + div.animate({ clip: ['rect(0px, 0px, 0px, 0px)', + 'rect(10px, 10px, 10px, 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).clip, 'rect(5px, 5px, 5px, 5px)', + 'Animated clip style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).clip, 'rect(20px, 20px, 20px, 20px)', + 'Animated clip style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).clip, 'rect(25px, 25px, 25px, 25px)', + 'Animated clip style at 50s of the third iteration'); +}, 'iterationComposite of <shape> type animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ width: ['calc(0vw + 0px)', 'calc(0vw + 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '5px', + 'Animated calc width style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).width, '20px', + 'Animated calc width style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '25px', + 'Animated calc width style at 50s of the third iteration'); +}, 'iterationComposite of <calc()> value animation'); + +test(function(t) { + var parent = createDiv(t); + parent.style.width = '100px'; + var div = createDiv(t); + parent.appendChild(div); + + var anim = + div.animate({ width: ['calc(0% + 0px)', 'calc(10% + 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, '10px', + // 100px * 5% + 5px + 'Animated calc width style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).width, + '40px', // 100px * (10% + 10%) + (10px + 10px) + 'Animated calc width style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).width, + '50px', // (40px + 60px) / 2 + 'Animated calc width style at 50s of the third iteration'); +}, 'iterationComposite of <calc()> value animation that the values can\'t' + + 'be reduced'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ opacity: [0, 0.4] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).opacity, '0.2', + 'Animated opacity style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).opacity, '0.8', + 'Animated opacity style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).opacity, '1', // (0.8 + 1.2) * 0.5 + 'Animated opacity style at 50s of the third iteration'); +}, 'iterationComposite of opacity animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ boxShadow: ['rgb(0, 0, 0) 0px 0px 0px 0px', + 'rgb(120, 120, 120) 10px 10px 10px 0px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(60, 60, 60) 5px 5px 5px 0px', + 'Animated box-shadow style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(240, 240, 240) 20px 20px 20px 0px', + 'Animated box-shadow style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).boxShadow, + 'rgb(255, 255, 255) 25px 25px 25px 0px', + 'Animated box-shadow style at 50s of the third iteration'); +}, 'iterationComposite of box-shadow animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['blur(0px)', 'blur(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, 'blur(5px)', + 'Animated filter blur style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, 'blur(20px)', + 'Animated filter blur style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, 'blur(25px)', + 'Animated filter blur style at 50s of the third iteration'); +}, 'iterationComposite of filter blur animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(1)', + 'brightness(180%)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(1.4)', + 'Animated filter brightness style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(2.6)', // brightness(1) + brightness(0.8) + brightness(0.8) + 'Animated filter brightness style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3)', // (brightness(2.6) + brightness(3.4)) * 0.5 + 'Animated filter brightness style at 50s of the third iteration'); +}, 'iterationComposite of filter brightness for different unit animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(0)', + 'brightness(1)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0.5)', + 'Animated filter brightness style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0)', // brightness(1) is an identity element, not accumulated. + 'Animated filter brightness style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(0.5)', // brightness(1) is an identity element, not accumulated. + 'Animated filter brightness style at 50s of the third iteration'); +}, 'iterationComposite of filter brightness animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['drop-shadow(rgb(0, 0, 0) 0px 0px 0px)', + 'drop-shadow(rgb(120, 120, 120) 10px 10px 10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(60, 60, 60) 5px 5px 5px)', + 'Animated filter drop-shadow style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(240, 240, 240) 20px 20px 20px)', + 'Animated filter drop-shadow style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'drop-shadow(rgb(255, 255, 255) 25px 25px 25px)', + 'Animated filter drop-shadow style at 50s of the third iteration'); +}, 'iterationComposite of filter drop-shadow animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(1) contrast(1)', + 'brightness(2) contrast(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(1.5) contrast(1.5)', + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3) contrast(3)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'brightness(3.5) contrast(3.5)', + 'Animated filter list at 50s of the third iteration'); +}, 'iterationComposite of same filter list animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['brightness(1) contrast(1)', + 'contrast(2) brightness(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'contrast(2) brightness(2)', // discrete + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + // We can't accumulate 'contrast(2) brightness(2)' onto + // the first list 'brightness(1) contrast(1)' because of + // mismatch of the order. + 'brightness(1) contrast(1)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + // We *can* accumulate 'contrast(2) brightness(2)' onto + // the same list 'contrast(2) brightness(2)' here. + 'contrast(4) brightness(4)', // discrete + 'Animated filter list at 50s of the third iteration'); +}, 'iterationComposite of discrete filter list because of mismatch ' + + 'of the order'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ filter: ['sepia(0)', + 'sepia(1) contrast(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(0.5) contrast(1.5)', + 'Animated filter list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(2) contrast(3)', + 'Animated filter list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).filter, + 'sepia(2.5) contrast(3.5)', + 'Animated filter list at 50s of the third iteration'); +}, 'iterationComposite of different length filter list animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['rotate(0deg)', 'rotate(180deg)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 0)', // rotate(90deg) + 'Animated transform(rotate) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 0, 0)', // rotate(360deg) + 'Animated transform(rotate) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 0)', // rotate(450deg) + 'Animated transform(rotate) style at 50s of the third iteration'); +}, 'iterationComposite of transform(rotate) animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['scale(0)', 'scale(1)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 0, 0, 0, 0, 0)', // scale(0); scale(1) is an identity element, + // not accumulated. + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0.5, 0, 0, 0.5, 0, 0)', // scale(0.5); scale(1) an identity + // element, not accumulated. + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iterationComposite of transform: [ scale(0), scale(1) ] animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['scale(1)', 'scale(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1.5, 0, 0, 1.5, 0, 0)', // scale(1.5) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 0, 0)', // scale(1 + (2 -1) + (2 -1)) + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3.5, 0, 0, 3.5, 0, 0)', // (scale(3) + scale(4)) * 0.5 + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iterationComposite of transform: [ scale(1), scale(2) ] animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['scale(0)', 'scale(2)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 0, 0)', // scale(1) + 'Animated transform(scale) style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(2, 0, 0, 2, 0, 0)', // (scale(0) + scale(2-1)*2) + 'Animated transform(scale) style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 0, 0)', // (scale(2) + scale(4)) * 0.5 + 'Animated transform(scale) style at 50s of the third iteration'); +}, 'iterationComposite of transform: scale(2) animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ transform: ['rotate(0deg) translateX(0px)', + 'rotate(180deg) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 5)', // rotate(90deg) translateX(5px) + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1, 0, 0, 1, 20, 0)', // rotate(360deg) translateX(20px) + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(0, 1, -1, 0, 0, 25)', // rotate(450deg) translateX(25px) + 'Animated transform list at 50s of the third iteration'); +}, 'iterationComposite of transform list animation'); + +test(function(t) { + var div = createDiv(t); + // The transform list whose order is mismatched is compounded, + // so below animation is the same as; + // from matrix(2, 0, 0, 2, 0, 0) to matrix(3, 0, 0, 3, 30, 0) + var anim = + div.animate({ transform: ['translateX(0px) scale(2)', + 'scale(3) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(2.5, 0, 0, 2.5, 15, 0)', // scale(2.5) (0px + 30px*2) / 2 + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(4, 0, 0, 4, 60, 0)', // scale(2+(3-2)*2) (0px + 30px*2) + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(5.5, 0, 0, 5.5, 135, 0)', // scale(4+7)/2 (60px + 210px) + 'Animated transform list at 50s of the third iteration'); +}, 'iterationComposite of transform list animation whose order is mismatched'); + +test(function(t) { + var div = createDiv(t); + // Even if each transform list does not have functions which exist in + // other pair of the list, we don't fill any missing functions at all, + // it's just computed as compounded matrices + // Below animation is the same as; + // from matrix(1, 0, 0, 1, 0, 0) to matrix(2, 0, 0, 2, 20, 0) + var anim = + div.animate({ transform: ['translateX(0px)', + 'scale(2) translateX(10px)'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(1.5, 0, 0, 1.5, 10, 0)', // scale(1.5) (0px + 10px*2) / 2 + 'Animated transform list at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3, 0, 0, 3, 40, 0)', // scale(1+(2-1)*2) (0px + 20px*2) + 'Animated transform list at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).transform, + 'matrix(3.5, 0, 0, 3.5, 80, 0)', // scale(3+4)/2 (40px + 20px) + 'Animated transform list at 50s of the third iteration'); +}, 'iterationComposite of transform list animation whose order is mismatched'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ marginLeft: ['10px', '20px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '15px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '50px', // 10px + 20px + 20px + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '55px', // (50px + 60px) * 0.5 + 'Animated margin-left style at 50s of the third iteration'); +}, 'iterationComposite starts with non-zero value animation'); + +test(function(t) { + var div = createDiv(t); + var anim = + div.animate({ marginLeft: ['10px', '-10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, + '0px', + 'Animated margin-left style at 50s of the first iteration'); + anim.currentTime = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, + '-10px', // 10px + -10px + -10px + 'Animated margin-left style at 0s of the third iteration'); + anim.currentTime += anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, + '-20px', // (-10px + -30px) * 0.5 + 'Animated margin-left style at 50s of the third iteration'); +}, 'iterationComposite with negative final value animation'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = + anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50s of the third iteration'); + + anim.effect.iterationComposite = 'replace'; + assert_equals(getComputedStyle(div).marginLeft, '5px', + 'Animated style at 50s of the third iteration'); + + anim.effect.iterationComposite = 'accumulate'; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50s of the third iteration'); +}, 'interationComposite changes'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ marginLeft: ['0px', '10px'] }, + { duration: 100 * MS_PER_SEC, + easing: 'linear', + iterations: 10, + iterationComposite: 'accumulate' }); + anim.pause(); + + anim.currentTime = + anim.effect.timing.duration * 2 + anim.effect.timing.duration / 2; + assert_equals(getComputedStyle(div).marginLeft, '25px', + 'Animated style at 50s of the third iteration'); + + // double its duration. + anim.effect.timing.duration = anim.effect.timing.duration * 2; + assert_equals(getComputedStyle(div).marginLeft, '12.5px', + 'Animated style at 25s of the first iteration'); + + // half of original. + anim.effect.timing.duration = anim.effect.timing.duration / 4; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Animated style at 50s of the fourth iteration'); +}, 'duration changes with iterationComposite(accumulate)'); + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html new file mode 100644 index 000000000..d16831281 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument.html @@ -0,0 +1,329 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly constructor tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#processing-a-keyframes-argument"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/keyframe-utils.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +'use strict'; + +// Test the "process a keyframe-like object" procedure. +// +// This file only tests the KeyframeEffectReadOnly constructor since it is +// assumed that the implementation of the KeyframeEffect constructor, +// Animatable.animate() method, and KeyframeEffect.setKeyframes() method will +// all share common machinery and it is not necessary to test each method. + +// Test that only animatable properties are accessed + +var gNonAnimatableProps = [ + 'animation', // Shorthands where all the longhand sub-properties are not + // animatable, are also not animatable. + 'animationDelay', + 'animationDirection', + 'animationDuration', + 'animationFillMode', + 'animationIterationCount', + 'animationName', + 'animationPlayState', + 'animationTimingFunction', + 'transition', + 'transitionDelay', + 'transitionDuration', + 'transitionProperty', + 'transitionTimingFunction', + 'display', + 'unsupportedProperty', +]; + +function TestKeyframe(testProp) { + var _propAccessCount = 0; + + Object.defineProperty(this, testProp, { + get: function() { _propAccessCount++; }, + enumerable: true + }); + + Object.defineProperty(this, 'propAccessCount', { + get: function() { return _propAccessCount; } + }); +} + +function GetTestKeyframeSequence(testProp) { + return [ new TestKeyframe(testProp) ] +} + +gNonAnimatableProps.forEach(function(prop) { + test(function(t) { + var testKeyframe = new TestKeyframe(prop); + + new KeyframeEffectReadOnly(null, testKeyframe); + + assert_equals(testKeyframe.propAccessCount, 0, 'Accessor not called'); + }, 'non-animatable property \'' + prop + '\' is not accessed when using' + + ' a property-indexed keyframe object'); +}); + +gNonAnimatableProps.forEach(function(prop) { + test(function(t) { + var testKeyframes = GetTestKeyframeSequence(prop); + + new KeyframeEffectReadOnly(null, testKeyframes); + + assert_equals(testKeyframes[0].propAccessCount, 0, 'Accessor not called'); + }, 'non-animatable property \'' + prop + '\' is not accessed when using' + + ' a keyframe sequence'); +}); + +// Test equivalent forms of property indexed and sequenced keyframe syntax + +function assertEquivalentKeyframeSyntax(keyframesA, keyframesB) { + var processedKeyframesA = new KeyframeEffectReadOnly(null, keyframesA).getKeyframes(); + var processedKeyframesB = new KeyframeEffectReadOnly(null, keyframesB).getKeyframes(); + assert_frame_lists_equal(processedKeyframesA, processedKeyframesB); +} + +var gEquivalentSyntaxTests = [ + { + description: 'two properties with one value', + indexedKeyframes: { + left: '100px', + opacity: ['1'], + }, + sequencedKeyframes: [ + {left: '100px', opacity: '1'}, + ], + }, + { + description: 'two properties with three values', + indexedKeyframes: { + left: ['10px', '100px', '150px'], + opacity: ['1', '0', '1'], + }, + sequencedKeyframes: [ + {left: '10px', opacity: '1'}, + {left: '100px', opacity: '0'}, + {left: '150px', opacity: '1'}, + ], + }, + { + description: 'two properties with different numbers of values', + indexedKeyframes: { + left: ['0px', '100px', '200px'], + opacity: ['0', '1'] + }, + sequencedKeyframes: [ + {left: '0px', opacity: '0'}, + {left: '100px'}, + {left: '200px', opacity: '1'}, + ], + }, + { + description: 'same offset applied to all keyframes', + indexedKeyframes: { + left: ['0px', '100px'], + offset: 0.5, + }, + sequencedKeyframes: [ + {left: '0px', offset: 0.5}, + {left: '100px', offset: 0.5}, + ], + }, + { + description: 'same easing applied to all keyframes', + indexedKeyframes: { + left: ['10px', '100px', '150px'], + opacity: ['1', '0', '1'], + easing: 'ease', + }, + sequencedKeyframes: [ + {left: '10px', opacity: '1', easing: 'ease'}, + {left: '100px', opacity: '0', easing: 'ease'}, + {left: '150px', opacity: '1', easing: 'ease'}, + ], + }, + { + description: 'same composite applied to all keyframes', + indexedKeyframes: { + left: ['0px', '100px'], + composite: 'add', + }, + sequencedKeyframes: [ + {left: '0px', composite: 'add'}, + {left: '100px', composite: 'add'}, + ], + }, +]; + +gEquivalentSyntaxTests.forEach(function({description, indexedKeyframes, sequencedKeyframes}) { + test(function(t) { + assertEquivalentKeyframeSyntax(indexedKeyframes, sequencedKeyframes); + }, 'Equivalent property indexed and sequenced keyframes: ' + description); +}); + +// Test handling of custom iterable objects. + +function createIterable(iterations) { + return { + [Symbol.iterator]() { + var i = 0; + return { + next() { + return iterations[i++]; + }, + }; + }, + }; +} + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: {left: '300px'}}, + {done: false, value: {left: '200px'}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px'}, + {offset: null, computedOffset: 0.5, easing: 'linear', left: '300px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px'}, + ]); +}, 'Custom iterator with basic keyframes.'); + +test(() => { + var keyframes = createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: {left: '300px'}}, + {done: false, value: {left: '200px'}}, + {done: true}, + ]); + keyframes.easing = 'ease-in-out'; + keyframes.offset = '0.1'; + var effect = new KeyframeEffect(null, keyframes); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px'}, + {offset: null, computedOffset: 0.5, easing: 'linear', left: '300px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px'}, + ]); +}, 'easing and offset are ignored on iterable objects.'); + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px', top: '200px'}}, + {done: false, value: {left: '300px'}}, + {done: false, value: {left: '200px', top: '100px'}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px', top: '200px'}, + {offset: null, computedOffset: 0.5, easing: 'linear', left: '300px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px', top: '100px'}, + ]); +}, 'Custom iterator with multiple properties specified.'); + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: {left: '250px', offset: 0.75}}, + {done: false, value: {left: '200px'}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', left: '100px'}, + {offset: 0.75, computedOffset: 0.75, easing: 'linear', left: '250px'}, + {offset: null, computedOffset: 1, easing: 'linear', left: '200px'}, + ]); +}, 'Custom iterator with offset specified.'); + +test(() => { + assert_throws({name: 'TypeError'}, function() { + new KeyframeEffect(null, createIterable([ + {done: false, value: {left: '100px'}}, + {done: false, value: 1234}, + {done: false, value: {left: '200px'}}, + {done: true}, + ])); + }); +}, 'Custom iterator with non object keyframe should throw.'); + +test(() => { + var effect = new KeyframeEffect(null, createIterable([ + {done: false, value: {left: ['100px', '200px']}}, + {done: true}, + ])); + assert_frame_lists_equal(effect.getKeyframes(), [ + {offset: null, computedOffset: 1, easing: 'linear', left: '100px,200px'} + ]); +}, 'Custom iterator with value list in keyframe should give bizarre string representation of list.'); + +test(function(t) { + var keyframe = {}; + Object.defineProperty(keyframe, 'width', {value: '200px'}); + Object.defineProperty(keyframe, 'height', { + value: '100px', + enumerable: true}); + assert_equals(keyframe.width, '200px', 'width of keyframe is readable'); + assert_equals(keyframe.height, '100px', 'height of keyframe is readable'); + var anim = createDiv(t).animate([keyframe, {height: '200px'}], 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', height: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', height: '200px'}, + ]); +}, 'Only enumerable properties on keyframes are considered'); + +test(function(t) { + var KeyframeParent = function() { this.width = '100px'; }; + KeyframeParent.prototype = { height: '100px' }; + var Keyframe = function() { this.top = '100px'; }; + Keyframe.prototype = Object.create(KeyframeParent.prototype); + Object.defineProperty(Keyframe.prototype, 'left', { + value: '100px', + enumerable: 'true'}); + var keyframe = new Keyframe(); + var anim = createDiv(t).animate([keyframe, {top: '200px'}], 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', top: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', top: '200px'}, + ]); +}, 'Only properties defined directly on keyframes are considered'); + +test(function(t) { + var keyframes = {}; + Object.defineProperty(keyframes, 'width', ['100px', '200px']); + Object.defineProperty(keyframes, 'height', { + value: ['100px', '200px'], + enumerable: true}); + var anim = createDiv(t).animate(keyframes, 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', height: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', height: '200px'}, + ]); +}, 'Only enumerable properties on property indexed keyframes are considered'); + +test(function(t) { + var KeyframesParent = function() { this.width = '100px'; }; + KeyframesParent.prototype = { height: '100px' }; + var Keyframes = function() { this.top = ['100px', '200px']; }; + Keyframes.prototype = Object.create(KeyframesParent.prototype); + Object.defineProperty(Keyframes.prototype, 'left', { + value: ['100px', '200px'], + enumerable: 'true'}); + var keyframes = new Keyframes(); + var anim = createDiv(t).animate(keyframes, 1); + assert_frame_lists_equal(anim.effect.getKeyframes(), [ + {offset: null, computedOffset: 0, easing: 'linear', top: '100px'}, + {offset: null, computedOffset: 1, easing: 'linear', top: '200px'}, + ]); +}, 'Only properties defined directly on property indexed keyframes are considered'); + +// FIXME: Test that properties are accessed in ascending order by Unicode +// codepoint +// (There is an existing test for this in +// keyframe-effect/constructor.html that should be moved here.) + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setKeyframes.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setKeyframes.html new file mode 100644 index 000000000..c951411de --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setKeyframes.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffect setKeyframes() tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-keyframeeffect-setkeyframes"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../resources/keyframe-utils.js"></script> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +'use strict'; + +var target = document.getElementById('target'); + +test(function(t) { + gEmptyKeyframeListTests.forEach(function(frame) { + var effect = new KeyframeEffect(target, {}); + effect.setKeyframes(frame); + assert_frame_lists_equal(effect.getKeyframes(), []); + }); +}, 'Keyframes can be replaced with an empty keyframe'); + +gPropertyIndexedKeyframesTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffect(target, {}); + effect.setKeyframes(subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), subtest.output); + }, 'Keyframes can be replaced with ' + subtest.desc); +}); + +gKeyframeSequenceTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffect(target, {}); + effect.setKeyframes(subtest.input); + assert_frame_lists_equal(effect.getKeyframes(), subtest.output); + }, 'Keyframes can be replaced with ' + subtest.desc); +}); + +gInvalidKeyframesTests.forEach(function(subtest) { + test(function(t) { + var effect = new KeyframeEffect(target, {}); + assert_throws(subtest.expected, function() { + effect.setKeyframes(subtest.input); + }); + }, 'KeyframeEffect constructor throws with ' + subtest.desc); +}); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setTarget.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setTarget.html new file mode 100644 index 000000000..2b07d3de6 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/setTarget.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Writable effect.target tests</title> +<link rel="help" + href="https://w3c.github.io/web-animations/#dom-keyframeeffect-target"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +var gKeyFrames = { 'marginLeft': ['0px', '100px'] }; + +test(function(t) { + var div = createDiv(t); + var effect = new KeyframeEffect(null, gKeyFrames, 100 * MS_PER_SEC); + effect.target = div; + + var anim = new Animation(effect, document.timeline); + anim.play(); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Value at 50% progress'); +}, 'Test setting target before constructing the associated animation'); + +test(function(t) { + var div = createDiv(t); + div.style.marginLeft = '10px'; + var effect = new KeyframeEffect(null, gKeyFrames, 100 * MS_PER_SEC); + var anim = new Animation(effect, document.timeline); + anim.play(); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '10px', + 'Value at 50% progress before setting new target'); + effect.target = div; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Value at 50% progress after setting new target'); +}, 'Test setting target from null to a valid target'); + +test(function(t) { + var div = createDiv(t); + div.style.marginLeft = '10px'; + var anim = div.animate(gKeyFrames, 100 * MS_PER_SEC); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '50px', + 'Value at 50% progress before clearing the target') + + anim.effect.target = null; + assert_equals(getComputedStyle(div).marginLeft, '10px', + 'Value after clearing the target') +}, 'Test setting target from a valid target to null'); + +test(function(t) { + var a = createDiv(t); + var b = createDiv(t); + a.style.marginLeft = '10px'; + b.style.marginLeft = '20px'; + var anim = a.animate(gKeyFrames, 100 * MS_PER_SEC); + + anim.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(a).marginLeft, '50px', + 'Value of 1st element (currently targeted) before ' + + 'changing the effect target'); + assert_equals(getComputedStyle(b).marginLeft, '20px', + 'Value of 2nd element (currently not targeted) before ' + + 'changing the effect target'); + anim.effect.target = b; + assert_equals(getComputedStyle(a).marginLeft, '10px', + 'Value of 1st element (currently not targeted) after ' + + 'changing the effect target'); + assert_equals(getComputedStyle(b).marginLeft, '50px', + 'Value of 2nd element (currently targeted) after ' + + 'changing the effect target'); + + // This makes sure the animation property is changed correctly on new + // targeted element. + anim.currentTime = 75 * MS_PER_SEC; + assert_equals(getComputedStyle(b).marginLeft, '75px', + 'Value of 2nd target (currently targeted) after ' + + 'changing the animation current time.'); +}, 'Test setting target from a valid target to another target'); + +test(function(t) { + var anim = createDiv(t).animate([ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + anim.effect.target = null; + + var frames = anim.effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[0].computedOffset, 0.0, '1st frame offset'); + assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset'); + assert_equals(frames[3].computedOffset, 1.0, 'last frame offset'); +}, 'Test falling back to distribute spacing mode after setting null target'); + +test(function(t) { + var effect = new KeyframeEffect(null, + [ { marginLeft: '0px' }, + { marginLeft: '-20px' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + var frames = effect.getKeyframes(); + var slots = frames.length - 1; + assert_equals(frames[1].computedOffset, 1.0 / slots, '2nd frame offset'); + assert_equals(frames[2].computedOffset, 2.0 / slots, '3rd frame offset'); +}, 'Test falling back to distribute spacing mode if there is no context ' + + 'element'); + +test(function(t) { + var div1 = createDiv(t); + var div2 = createDiv(t); + div1.style.marginLeft = '-20px'; + div2.style.marginLeft = '-50px'; + var child1 = document.createElement('div'); + var child2 = document.createElement('div'); + div1.appendChild(child1); + div2.appendChild(child2); + // body + // / \ + // div1 div2 + // (-20px) (-50px) + // | | + // child1 child2 + var anim = child1.animate([ { marginLeft: '0px' }, + { marginLeft: 'inherit' }, + { marginLeft: '100px' }, + { marginLeft: '50px' } ], + { duration: 100 * MS_PER_SEC, + spacing: 'paced(margin-left)' }); + + var frames = anim.effect.getKeyframes(); + var cumDist = [0, 20, 140, 190]; + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset'); + + anim.effect.target = child2; + frames = anim.effect.getKeyframes(); + cumDist = [0, 50, 200, 250]; + assert_equals(frames[1].computedOffset, cumDist[1] / cumDist[3], + '2nd frame offset after setting a new target'); + assert_equals(frames[2].computedOffset, cumDist[2] / cumDist[3], + '3rd frame offset after setting a new target'); +}, 'Test paced spacing mode after setting a new target'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/spacing.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/spacing.html new file mode 100644 index 000000000..612a3af8d --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/spacing.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffect spacing attribute tests</title> +<link rel="help" + href="https://w3c.github.io/web-animations/#dom-keyframeeffect-spacing"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +"use strict"; + +test(function(t) { + var anim = createDiv(t).animate(null); + assert_throws(new TypeError, function() { + anim.effect.spacing = ''; + }); +}, 'Test throwing TypeError if using empty string'); + +test(function(t) { + var anim = createDiv(t).animate(null); + assert_throws(new TypeError, function() { + anim.effect.spacing = 'dist'; + }); +}, 'Test throwing TypeError if not using the correct keyword'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(A)'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(--bg-color)'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using CSS variables'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(animation-duration)'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a non-animatable ' + + 'property'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'distribute'; + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test spacing value if setting distribute'); + +test(function(t) { + var anim = createDiv(t).animate(null); + anim.effect.spacing = 'paced(transform)'; + assert_equals(anim.effect.spacing, 'paced(transform)', 'spacing mode'); +}, 'Test spacing value if setting paced'); + +</script> +</body> |