diff options
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces')
41 files changed, 5589 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html new file mode 100644 index 000000000..a54298aa4 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animatable/animate.html @@ -0,0 +1,180 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animatable.animate tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animatable-animate"> +<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> +<iframe width="10" height="10" id="iframe"></iframe> +<script> +'use strict'; + +// Tests on Element + +test(function(t) { + var div = createDiv(t); + var anim = div.animate(null); + assert_class_string(anim, 'Animation', 'Returned object is an Animation'); +}, 'Element.animate() creates an Animation object'); + +test(function(t) { + var iframe = window.frames[0]; + var div = createDiv(t, iframe.document); + var anim = Element.prototype.animate.call(div, null); + assert_equals(Object.getPrototypeOf(anim), iframe.Animation.prototype, + 'The prototype of the created Animation is that defined on' + + ' the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim), Animation.prototype, + 'The prototype of the created Animation is NOT that of' + + ' the current global'); +}, 'Element.animate() creates an Animation object in the relevant realm of' + + ' the target element'); + +test(function(t) { + var div = createDiv(t); + var anim = Element.prototype.animate.call(div, null); + assert_class_string(anim.effect, 'KeyframeEffect', + 'Returned Animation has a KeyframeEffect'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect'); + +test(function(t) { + var iframe = window.frames[0]; + var div = createDiv(t, iframe.document); + var anim = Element.prototype.animate.call(div, null); + assert_equals(Object.getPrototypeOf(anim.effect), + iframe.KeyframeEffect.prototype, + 'The prototype of the created KeyframeEffect is that defined on' + + ' the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim.effect), + KeyframeEffect.prototype, + 'The prototype of the created KeyframeEffect is NOT that of' + + ' the current global'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect' + + ' that is created in the relevant realm of the target element'); + +test(function(t) { + var iframe = window.frames[0]; + var div = createDiv(t, iframe.document); + var anim = div.animate(null); + assert_equals(Object.getPrototypeOf(anim.effect.timing), + iframe.AnimationEffectTiming.prototype, + 'The prototype of the created AnimationEffectTiming is that' + + ' defined on the relevant global for the target element'); + assert_not_equals(Object.getPrototypeOf(anim.effect.timing), + AnimationEffectTiming.prototype, + 'The prototype of the created AnimationEffectTiming is NOT' + + ' that of the current global'); +}, 'Element.animate() creates an Animation object with a KeyframeEffect' + + ' whose AnimationEffectTiming object is created in the relevant realm' + + ' of the target element'); + +gPropertyIndexedKeyframesTests.forEach(function(subtest) { + test(function(t) { + var div = createDiv(t); + var anim = div.animate(subtest.input, 2000); + assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output); + }, 'Element.animate() accepts ' + subtest.desc); +}); + +gKeyframeSequenceTests.forEach(function(subtest) { + test(function(t) { + var div = createDiv(t); + var anim = div.animate(subtest.input, 2000); + assert_frame_lists_equal(anim.effect.getKeyframes(), subtest.output); + }, 'Element.animate() accepts ' + subtest.desc); +}); + +gInvalidKeyframesTests.forEach(function(subtest) { + test(function(t) { + var div = createDiv(t); + assert_throws(subtest.expected, function() { + div.animate(subtest.input, 2000); + }); + }, 'Element.animate() does not accept ' + subtest.desc); +}); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.effect.timing.duration, 2000); + // Also check that unspecified parameters receive their default values + assert_equals(anim.effect.timing.fill, 'auto'); +}, 'Element.animate() accepts a double as an options argument'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: Infinity, fill: 'forwards' }); + assert_equals(anim.effect.timing.duration, Infinity); + assert_equals(anim.effect.timing.fill, 'forwards'); + // Also check that unspecified parameters receive their default values + assert_equals(anim.effect.timing.direction, 'normal'); +}, 'Element.animate() accepts a KeyframeAnimationOptions argument'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }); + assert_equals(anim.effect.timing.duration, 'auto'); +}, 'Element.animate() accepts an absent options argument'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.id, ''); +}, 'Element.animate() correctly sets the id attribute when no id is specified'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, { id: 'test' }); + assert_equals(anim.id, 'test'); +}, 'Element.animate() correctly sets the id attribute'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.timeline, document.timeline); +}, 'Element.animate() correctly sets the Animation\'s timeline'); + +async_test(function(t) { + var iframe = document.createElement('iframe'); + iframe.width = 10; + iframe.height = 10; + + iframe.addEventListener('load', t.step_func(function() { + var div = createDiv(t, iframe.contentDocument); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.timeline, iframe.contentDocument.timeline); + iframe.remove(); + t.done(); + })); + + document.body.appendChild(iframe); +}, 'Element.animate() correctly sets the Animation\'s timeline when ' + + 'triggered on an element in a different document'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_equals(anim.playState, 'pending'); +}, 'Element.animate() calls play on the Animation'); + +// Tests on CSSPseudoElement + +test(function(t) { + var pseudoTarget = createPseudo(t, 'before'); + var anim = pseudoTarget.animate(null); + assert_class_string(anim, 'Animation', 'The returned object is an Animation'); +}, 'CSSPseudoElement.animate() creates an Animation object'); + +test(function(t) { + var pseudoTarget = createPseudo(t, 'before'); + var anim = pseudoTarget.animate(null); + assert_equals(anim.effect.target, pseudoTarget, + 'The returned Animation targets to the correct object'); +}, 'CSSPseudoElement.animate() creates an Animation object targeting ' + + 'to the correct CSSPseudoElement object'); +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html b/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html new file mode 100644 index 000000000..f8f174abd --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/cancel.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.cancel()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-cancel"> +<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'; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({transform: ['translate(100px)', 'translate(100px)']}, + 100 * MS_PER_SEC); + return animation.ready.then(function() { + assert_not_equals(getComputedStyle(div).transform, 'none', + 'transform style is animated before cancelling'); + animation.cancel(); + assert_equals(getComputedStyle(div).transform, 'none', + 'transform style is no longer animated after cancelling'); + }); +}, 'Animated style is cleared after calling Animation.cancel()'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({marginLeft: ['100px', '200px']}, + 100 * MS_PER_SEC); + animation.effect.timing.easing = 'linear'; + animation.cancel(); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + + animation.currentTime = 50 * MS_PER_SEC; + assert_equals(getComputedStyle(div).marginLeft, '150px', + 'margin-left style is updated when cancelled animation is' + + ' seeked'); +}, 'After cancelling an animation, it can still be seeked'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({marginLeft:['100px', '200px']}, + 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.cancel(); + assert_equals(getComputedStyle(div).marginLeft, '0px', + 'margin-left style is not animated after cancelling'); + animation.play(); + assert_equals(getComputedStyle(div).marginLeft, '100px', + 'margin-left style is animated after re-starting animation'); + return animation.ready; + }).then(function() { + assert_equals(animation.playState, 'running', + 'Animation succeeds in running after being re-started'); + }); +}, 'After cancelling an animation, it can still be re-used'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html b/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html new file mode 100644 index 000000000..4f76194b6 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/constructor.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation constructor tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#dom-animation-animation"> +<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> +<body> +<div id="log"></div> +<div id="target"></div> +<script> +"use strict"; + +var gTarget = document.getElementById("target"); + +function createEffect() { + return new KeyframeEffectReadOnly(gTarget, { opacity: [0, 1] }); +} + +function createNull() { + return null; +} + +var gTestArguments = [ + { + createEffect: createNull, + timeline: null, + expectedTimeline: null, + expectedTimelineDescription: "null", + description: "with null effect and null timeline" + }, + { + createEffect: createNull, + timeline: document.timeline, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with null effect and non-null timeline" + }, + { + createEffect: createNull, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with null effect and no timeline parameter" + }, + { + createEffect: createEffect, + timeline: null, + expectedTimeline: null, + expectedTimelineDescription: "null", + description: "with non-null effect and null timeline" + }, + { + createEffect: createEffect, + timeline: document.timeline, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with non-null effect and non-null timeline" + }, + { + createEffect: createEffect, + expectedTimeline: document.timeline, + expectedTimelineDescription: "document.timeline", + description: "with non-null effect and no timeline parameter" + }, +]; + +gTestArguments.forEach(function(args) { + test(function(t) { + var effect = args.createEffect(); + var animation = new Animation(effect, args.timeline); + + assert_not_equals(animation, null, + "An animation sohuld be created"); + assert_equals(animation.effect, effect, + "Animation returns the same effect passed to " + + "the Constructor"); + assert_equals(animation.timeline, args.expectedTimeline, + "Animation timeline should be " + args.expectedTimelineDescription); + assert_equals(animation.playState, "idle", + "Animation.playState should be initially 'idle'"); + }, "Animation can be constructed " + args.description); +}); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, + { left: ["10px", "20px"] }, + { duration: 10000, + fill: "forwards" }); + var anim = new Animation(effect, document.timeline); + anim.pause(); + assert_equals(effect.getComputedTiming().progress, 0.0); + anim.currentTime += 5000; + assert_equals(effect.getComputedTiming().progress, 0.5); + anim.finish(); + assert_equals(effect.getComputedTiming().progress, 1.0); +}, "Animation constructed by an effect with null target runs normally"); + +async_test(function(t) { + var iframe = document.createElement('iframe'); + + iframe.addEventListener('load', t.step_func(function() { + var div = createDiv(t, iframe.contentDocument); + var effect = new KeyframeEffectReadOnly(div, null, 10000); + var anim = new Animation(effect); + assert_equals(anim.timeline, document.timeline); + iframe.remove(); + t.done(); + })); + + document.body.appendChild(iframe); +}, "Animation constructed with a keyframe that target element is in iframe"); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html b/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html new file mode 100644 index 000000000..d881a96a7 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/effect.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.effect tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-effect"> +<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 = new Animation(); + assert_equals(anim.effect, null, "initial effect is null"); + + var newEffect = new KeyframeEffectReadOnly(createDiv(t), null); + anim.effect = newEffect; + assert_equals(anim.effect, newEffect, "new effect is set"); +}, "effect is set correctly."); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/finish.html b/testing/web-platform/tests/web-animations/interfaces/Animation/finish.html new file mode 100644 index 000000000..9b9c4c710 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/finish.html @@ -0,0 +1,246 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.finish()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-finish"> +<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': ['100px', '200px'] }; + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.playbackRate = 0; + + assert_throws({name: 'InvalidStateError'}, function() { + animation.finish(); + }); +}, 'Test exceptions when finishing non-running animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, + {duration : 100 * MS_PER_SEC, + iterations : Infinity}); + + assert_throws({name: 'InvalidStateError'}, function() { + animation.finish(); + }); +}, 'Test exceptions when finishing infinite animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.finish(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'After finishing, the currentTime should be set to the end ' + + 'of the active duration'); +}, 'Test finishing of animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + // 1s past effect end + animation.currentTime = + animation.effect.getComputedTiming().endTime + 1 * MS_PER_SEC; + animation.finish(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'After finishing, the currentTime should be set back to the ' + + 'end of the active duration'); +}, 'Test finishing of animation with a current time past the effect end'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.currentTime = 100 * MS_PER_SEC; + return animation.finished.then(function() { + animation.playbackRate = -1; + animation.finish(); + + assert_equals(animation.currentTime, 0, + 'After finishing a reversed animation the currentTime ' + + 'should be set to zero'); + }); +}, 'Test finishing of reversed animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.currentTime = 100 * MS_PER_SEC; + return animation.finished.then(function() { + animation.playbackRate = -1; + animation.currentTime = -1000; + animation.finish(); + + assert_equals(animation.currentTime, 0, + 'After finishing a reversed animation the currentTime ' + + 'should be set back to zero'); + }); +}, 'Test finishing of reversed animation with a current time less than zero'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + return animation.ready.then(function() { + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a paused animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC, + 'The start time of a paused animation should be set ' + + 'after calling finish()'); + }); +}, 'Test finish() while paused'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + // Update playbackRate so we can test that the calculated startTime + // respects it + animation.playbackRate = 2; + // While animation is still pause-pending call finish() + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC / 2, + 'The start time of a pause-pending animation should ' + + 'be set after calling finish()'); +}, 'Test finish() while pause-pending with positive playbackRate'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.pause(); + animation.playbackRate = -2; + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished" after finish() is called'); + assert_equals(animation.startTime, animation.timeline.currentTime, + 'The start time of a pause-pending animation should be ' + + 'set after calling finish()'); +}, 'Test finish() while pause-pending with negative playbackRate'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + animation.playbackRate = 0.5; + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a play-pending animation should become ' + + '"finished" after finish() is called'); + assert_times_equal(animation.startTime, + animation.timeline.currentTime - 100 * MS_PER_SEC / 0.5, + 'The start time of a play-pending animation should ' + + 'be set after calling finish()'); +}, 'Test finish() while play-pending'); + +// FIXME: Add a test for when we are play-pending without an active timeline. +// - In that case even after calling finish() we should still be pending but +// the current time should be updated + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.pause(); + animation.play(); + // We are now in the unusual situation of being play-pending whilst having + // a resolved start time. Check that finish() still triggers a transition + // to the finished state immediately. + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'After aborting a pause then calling finish() the play ' + + 'state of an animation should become "finished" immediately'); + }); +}, 'Test finish() during aborted pause'); + +promise_test(function(t) { + var div = createDiv(t); + div.style.marginLeft = '10px'; + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.finish(); + var marginLeft = parseFloat(getComputedStyle(div).marginLeft); + + assert_equals(marginLeft, 10, + 'The computed style should be reset when finish() is ' + + 'called'); + }); +}, 'Test resetting of computed style'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(gKeyFrames, 100 * MS_PER_SEC); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.finish(); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation.finish()'); + }); +}, 'Test finish() resolves finished promise synchronously'); + +promise_test(function(t) { + var effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + var animation = new Animation(effect, document.timeline); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.finish(); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation.finish()'); + }); +}, 'Test finish() resolves finished promise synchronously with an animation ' + + 'without a target'); + +promise_test(function(t) { + var effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + var animation = new Animation(effect, document.timeline); + animation.play(); + + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.currentTime = animation.effect.getComputedTiming().endTime - 1; + return waitForAnimationFrames(2); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after ' + + 'Animation finishes normally'); + }); +}, 'Test normally finished animation resolves finished promise synchronously ' + + 'with an animation without a target'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html b/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html new file mode 100644 index 000000000..d56de1b03 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/finished.html @@ -0,0 +1,370 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.finished</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-finished"> +<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"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + return animation.ready.then(function() { + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same object when playing starts'); + animation.pause(); + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise does not change when pausing'); + animation.play(); + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise does not change when play() unpauses'); + + animation.currentTime = 100 * MS_PER_SEC; + + return animation.finished; + }).then(function() { + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same object when playing completes'); + }); +}, 'Test pausing then playing does not change the finished promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same object when playing completes'); + animation.play(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise changes when replaying animation'); + + previousFinishedPromise = animation.finished; + animation.play(); + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is the same after redundant play() call'); + + }); +}, 'Test restarting a finished animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise; + animation.finish(); + return animation.finished.then(function() { + previousFinishedPromise = animation.finished; + animation.playbackRate = -1; + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should be replaced when reversing a ' + + 'finished promise'); + animation.currentTime = 0; + return animation.finished; + }).then(function() { + previousFinishedPromise = animation.finished; + animation.play(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise is replaced after play() call on ' + + 'finished, reversed animation'); + }); +}, 'Test restarting a reversed finished animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + animation.currentTime = 100 * MS_PER_SEC + 1000; + assert_equals(animation.finished, previousFinishedPromise, + 'Finished promise is unchanged jumping past end of ' + + 'finished animation'); + }); +}, 'Test redundant finishing of animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + // Setup callback to run if finished promise is resolved + var finishPromiseResolved = false; + animation.finished.then(function() { + finishPromiseResolved = true; + }); + return animation.ready.then(function() { + // Jump to mid-way in interval and pause + animation.currentTime = 100 * MS_PER_SEC / 2; + animation.pause(); + return animation.ready; + }).then(function() { + // Jump to the end + // (But don't use finish() since that should unpause as well) + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }).then(function() { + assert_false(finishPromiseResolved, + 'Finished promise should not resolve when paused'); + }); +}, 'Finished promise does not resolve when paused'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + // Setup callback to run if finished promise is resolved + var finishPromiseResolved = false; + animation.finished.then(function() { + finishPromiseResolved = true; + }); + return animation.ready.then(function() { + // Jump to mid-way in interval and pause + animation.currentTime = 100 * MS_PER_SEC / 2; + animation.pause(); + // Jump to the end + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }).then(function() { + assert_false(finishPromiseResolved, + 'Finished promise should not resolve when pause-pending'); + }); +}, 'Finished promise does not resolve when pause-pending'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.finish(); + return animation.finished.then(function(resolvedAnimation) { + assert_equals(resolvedAnimation, animation, + 'Object identity of animation passed to Promise callback' + + ' matches the animation object owning the Promise'); + }); +}, 'The finished promise is fulfilled with its Animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + + // Set up listeners on finished promise + var retPromise = animation.finished.then(function() { + assert_unreached('finished promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'finished promise is rejected with AbortError'); + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change after the original is ' + + 'rejected'); + }); + + animation.cancel(); + + return retPromise; +}, 'finished promise is rejected when an animation is cancelled by calling ' + + 'cancel()'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + animation.cancel(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'A new finished promise should be created when' + + ' cancelling a finished animation'); + }); +}, 'cancelling an already-finished animation replaces the finished promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.cancel(); + // The spec says we still create a new finished promise and reject the old + // one even if we're already idle. That behavior might change, but for now + // test that we do that. + var retPromise = animation.finished.catch(function(err) { + assert_equals(err.name, 'AbortError', + 'finished promise is rejected with AbortError'); + }); + + // Redundant call to cancel(); + var previousFinishedPromise = animation.finished; + animation.cancel(); + assert_not_equals(animation.finished, previousFinishedPromise, + 'A redundant call to cancel() should still generate a new' + + ' finished promise'); + return retPromise; +}, 'cancelling an idle animation still replaces the finished promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + const HALF_DUR = 100 * MS_PER_SEC / 2; + const QUARTER_DUR = 100 * MS_PER_SEC / 4; + var gotNextFrame = false; + var currentTimeBeforeShortening; + animation.currentTime = HALF_DUR; + return animation.ready.then(function() { + currentTimeBeforeShortening = animation.currentTime; + animation.effect.timing.duration = QUARTER_DUR; + // Below we use gotNextFrame to check that shortening of the animation + // duration causes the finished promise to resolve, rather than it just + // getting resolved on the next animation frame. This relies on the fact + // that the promises are resolved as a micro-task before the next frame + // happens. + waitForAnimationFrames(1).then(function() { + gotNextFrame = true; + }); + + return animation.finished; + }).then(function() { + assert_false(gotNextFrame, 'shortening of the animation duration should ' + + 'resolve the finished promise'); + assert_equals(animation.currentTime, currentTimeBeforeShortening, + 'currentTime should be unchanged when duration shortened'); + var previousFinishedPromise = animation.finished; + animation.effect.timing.duration = 100 * MS_PER_SEC; + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change after lengthening the ' + + 'duration causes the animation to become active'); + }); +}, 'Test finished promise changes for animation duration changes'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var retPromise = animation.ready.then(function() { + animation.playbackRate = 0; + animation.currentTime = 100 * MS_PER_SEC + 1000; + return waitForAnimationFrames(2); + }); + + animation.finished.then(t.step_func(function() { + assert_unreached('finished promise should not resolve when playbackRate ' + + 'is zero'); + })); + + return retPromise; +}, 'Test finished promise changes when playbackRate == 0'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.playbackRate = -1; + return animation.finished; + }); +}, 'Test finished promise resolves when reaching to the natural boundary.'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + return animation.finished.then(function() { + animation.currentTime = 0; + assert_not_equals(animation.finished, previousFinishedPromise, + 'Finished promise should change once a prior ' + + 'finished promise resolved and the animation ' + + 'falls out finished state'); + }); +}, 'Test finished promise changes when a prior finished promise resolved ' + + 'and the animation falls out finished state'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.currentTime = 100 * MS_PER_SEC; + animation.currentTime = 100 * MS_PER_SEC / 2; + assert_equals(animation.finished, previousFinishedPromise, + 'No new finished promise generated when finished state ' + + 'is checked asynchronously'); +}, 'Test no new finished promise generated when finished state ' + + 'is checked asynchronously'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var previousFinishedPromise = animation.finished; + animation.finish(); + animation.currentTime = 100 * MS_PER_SEC / 2; + assert_not_equals(animation.finished, previousFinishedPromise, + 'New finished promise generated when finished state ' + + 'is checked synchronously'); +}, 'Test new finished promise generated when finished state ' + + 'is checked synchronously'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + return animation.ready.then(function() { + animation.finish(); + animation.currentTime = 100 * MS_PER_SEC / 2; + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved even if ' + + 'the finished state is changed soon'); + }); + +}, 'Test synchronous finished promise resolved even if finished state ' + + 'is changed soon'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var resolvedFinished = false; + animation.finished.then(function() { + resolvedFinished = true; + }); + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.finish(); + }).then(function() { + assert_true(resolvedFinished, + 'Animation.finished should be resolved soon after finish() is ' + + 'called even if there are other asynchronous promises just before it'); + }); +}, 'Test synchronous finished promise resolved even if asynchronous ' + + 'finished promise happens just before synchronous promise'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.finished.then(t.step_func(function() { + assert_unreached('Animation.finished should not be resolved'); + })); + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.currentTime = 100 * MS_PER_SEC / 2; + }); +}, 'Test finished promise is not resolved when the animation ' + + 'falls out finished state immediately'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.finished.then(t.step_func(function() { + assert_unreached('Animation.finished should not be resolved'); + })); + animation.currentTime = 0; + }); + +}, 'Test finished promise is not resolved once the animation ' + + 'falls out finished state even though the current finished ' + + 'promise is generated soon after animation state became finished'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/id.html b/testing/web-platform/tests/web-animations/interfaces/Animation/id.html new file mode 100644 index 000000000..2fadd5623 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/id.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.id</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-id"> +<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 div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + assert_equals(animation.id, '', 'id for Animation is initially empty'); +}, 'Animation.id initial value'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.id = 'anim'; + + assert_equals(animation.id, 'anim', 'animation.id reflects the value set'); +}, 'Animation.id setter'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/oncancel.html b/testing/web-platform/tests/web-animations/interfaces/Animation/oncancel.html new file mode 100644 index 000000000..b8d9ced61 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/oncancel.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.oncancel</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-oncancel"> +<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"; + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var finishedTimelineTime; + animation.finished.then().catch(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.oncancel = t.step_func_done(function(event) { + assert_equals(event.currentTime, null, + 'event.currentTime should be null'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is rejected'); + }); + + animation.cancel(); +}, 'oncancel event is fired when animation.cancel() is called.'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/onfinish.html b/testing/web-platform/tests/web-animations/interfaces/Animation/onfinish.html new file mode 100644 index 000000000..50e5bed6b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/onfinish.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.onfinish</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-onfinish"> +<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"; + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var finishedTimelineTime; + animation.finished.then(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.onfinish = t.step_func_done(function(event) { + assert_equals(event.currentTime, 0, + 'event.currentTime should be zero'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is resolved'); + }); + + animation.playbackRate = -1; +}, 'onfinish event is fired when the currentTime < 0 and ' + + 'the playbackRate < 0'); + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + var finishedTimelineTime; + animation.finished.then(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.onfinish = t.step_func_done(function(event) { + assert_equals(event.currentTime, 100 * MS_PER_SEC, + 'event.currentTime should be the effect end'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is resolved'); + }); + + animation.currentTime = 100 * MS_PER_SEC; +}, 'onfinish event is fired when the currentTime > 0 and ' + + 'the playbackRate > 0'); + +async_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + var finishedTimelineTime; + animation.finished.then(function() { + finishedTimelineTime = animation.timeline.currentTime; + }); + + animation.onfinish = t.step_func_done(function(event) { + assert_equals(event.currentTime, 100 * MS_PER_SEC, + 'event.currentTime should be the effect end'); + assert_equals(event.timelineTime, finishedTimelineTime, + 'event.timelineTime should equal to the animation timeline ' + + 'when finished promise is resolved'); + }); + + animation.finish(); +}, 'onfinish event is fired when animation.finish() is called'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + animation.onfinish = function(event) { + assert_unreached('onfinish event should not be fired'); + }; + + animation.currentTime = 100 * MS_PER_SEC / 2; + animation.pause(); + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }); +}, 'onfinish event is not fired when paused'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.onfinish = function(event) { + assert_unreached('onfinish event should not be fired'); + }; + + return animation.ready.then(function() { + animation.playbackRate = 0; + animation.currentTime = 100 * MS_PER_SEC; + return waitForAnimationFrames(2); + }); +}, 'onfinish event is not fired when the playbackRate is zero'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.onfinish = function(event) { + assert_unreached('onfinish event should not be fired'); + }; + + return animation.ready.then(function() { + animation.currentTime = 100 * MS_PER_SEC; + animation.currentTime = 100 * MS_PER_SEC / 2; + return waitForAnimationFrames(2); + }); +}, 'onfinish event is not fired when the animation falls out ' + + 'finished state immediately'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/pause.html b/testing/web-platform/tests/web-animations/interfaces/Animation/pause.html new file mode 100644 index 000000000..bcfaf4a9b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/pause.html @@ -0,0 +1,98 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.pause()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-pause"> +<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"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + var previousCurrentTime = animation.currentTime; + + return animation.ready.then(waitForAnimationFrames(1)).then(function() { + assert_true(animation.currentTime >= previousCurrentTime, + 'currentTime is initially increasing'); + animation.pause(); + return animation.ready; + }).then(function() { + previousCurrentTime = animation.currentTime; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(animation.currentTime, previousCurrentTime, + 'currentTime does not increase after calling pause()'); + }); +}, 'pause() a running animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + + // Go to idle state then pause + animation.cancel(); + animation.pause(); + + assert_equals(animation.currentTime, 0, 'currentTime is set to 0'); + assert_equals(animation.startTime, null, 'startTime is not set'); + assert_equals(animation.playState, 'pending', 'initially pause-pending'); + + // Check it still resolves as expected + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused', + 'resolves to paused state asynchronously'); + assert_equals(animation.currentTime, 0, + 'keeps the initially set currentTime'); + }); +}, 'pause() from idle'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + animation.cancel(); + animation.playbackRate = -1; + animation.pause(); + + assert_equals(animation.currentTime, 1000 * MS_PER_SEC, + 'currentTime is set to the effect end'); + + return animation.ready.then(function() { + assert_equals(animation.currentTime, 1000 * MS_PER_SEC, + 'keeps the initially set currentTime'); + }); +}, 'pause() from idle with a negative playbackRate'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 1000 * MS_PER_SEC, + iterations: Infinity}); + animation.cancel(); + animation.playbackRate = -1; + + assert_throws('InvalidStateError', + function () { animation.pause(); }, + 'Expect InvalidStateError exception on calling pause() ' + + 'from idle with a negative playbackRate and ' + + 'infinite-duration animation'); +}, 'pause() from idle with a negative playbackRate and endless effect'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 1000 * MS_PER_SEC); + return animation.ready + .then(function(animation) { + animation.finish(); + animation.pause(); + return animation.ready; + }).then(function(animation) { + assert_equals(animation.currentTime, 1000 * MS_PER_SEC, + 'currentTime after pausing finished animation'); + }); +}, 'pause() on a finished animation'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/play.html b/testing/web-platform/tests/web-animations/interfaces/Animation/play.html new file mode 100644 index 000000000..767d8df17 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/play.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.play()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-play"> +<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'; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({ transform: ['none', 'translate(10px)']}, + { duration : 100 * MS_PER_SEC, + iterations : Infinity}); + return animation.ready.then(function() { + // Seek to a time outside the active range so that play() will have to + // snap back to the start + animation.currentTime = -5 * MS_PER_SEC; + animation.playbackRate = -1; + + assert_throws('InvalidStateError', + function () { animation.play(); }, + 'Expected InvalidStateError exception on calling play() ' + + 'with a negative playbackRate and infinite-duration ' + + 'animation'); + }); +}, 'play() throws when seeking an infinite-duration animation played in ' + + 'reverse'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/playState.html b/testing/web-platform/tests/web-animations/interfaces/Animation/playState.html new file mode 100644 index 000000000..15af526cd --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/playState.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.playState</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-playstate"> +<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'; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + assert_equals(animation.playState, 'pending'); + return animation.ready.then(function() { + assert_equals(animation.playState, 'running'); + }); +}, 'Animation.playState reports \'pending\'->\'running\' when initially ' + + 'played'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.pause(); + + assert_equals(animation.playState, 'pending'); + return animation.ready.then(function() { + assert_equals(animation.playState, 'paused'); + }); +}, 'Animation.playState reports \'pending\'->\'paused\' when pausing'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.cancel(); + assert_equals(animation.playState, 'idle'); +}, 'Animation.playState is \'idle\' when canceled.'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.cancel(); + animation.currentTime = 50 * MS_PER_SEC; + assert_equals(animation.playState, 'paused', + 'After seeking an idle animation, it is effectively paused'); +}, 'Animation.playState is \'paused\' after cancelling an animation, ' + + 'seeking it makes it paused'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html b/testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html new file mode 100644 index 000000000..c923df6b4 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/playbackRate.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.playbackRate</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-playbackrate"> +<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"; + +function assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + description) { + var accuracy = 0.001; /* accuracy of DOMHighResTimeStamp */ + var animationCurrentTimeDifference = + animation.currentTime - previousAnimationCurrentTime; + var timelineCurrentTimeDifference = + animation.timeline.currentTime - previousTimelineCurrentTime; + + assert_approx_equals(animationCurrentTimeDifference, + timelineCurrentTimeDifference * animation.playbackRate, + accuracy, + description); +} + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(null, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.currentTime = 7 * MS_PER_SEC; // ms + animation.playbackRate = 0.5; + + assert_equals(animation.currentTime, 7 * MS_PER_SEC, + 'Reducing Animation.playbackRate should not change the currentTime ' + + 'of a playing animation'); + animation.playbackRate = 2; + assert_equals(animation.currentTime, 7 * MS_PER_SEC, + 'Increasing Animation.playbackRate should not change the currentTime ' + + 'of a playing animation'); + }); +}, 'Test the initial effect of setting playbackRate on currentTime'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(null, 100 * MS_PER_SEC); + animation.playbackRate = 2; + var previousTimelineCurrentTime; + var previousAnimationCurrentTime; + return animation.ready.then(function() { + previousAnimationCurrentTime = animation.currentTime; + previousTimelineCurrentTime = animation.timeline.currentTime; + return waitForAnimationFrames(1); + }).then(function() { + assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + 'animation.currentTime should be 2 times faster than timeline.'); + }); +}, 'Test the effect of setting playbackRate on currentTime'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate(null, 100 * MS_PER_SEC); + animation.playbackRate = 2; + var previousTimelineCurrentTime; + var previousAnimationCurrentTime; + return animation.ready.then(function() { + previousAnimationCurrentTime = animation.currentTime; + previousTimelineCurrentTime = animation.timeline.currentTime; + animation.playbackRate = 1; + return waitForAnimationFrames(1); + }).then(function() { + assert_equals(animation.playbackRate, 1, + 'sanity check: animation.playbackRate is still 1.'); + assert_playbackrate(animation, + previousAnimationCurrentTime, + previousTimelineCurrentTime, + 'animation.currentTime should be the same speed as timeline now.'); + }); +}, 'Test the effect of setting playbackRate while playing animation'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/ready.html b/testing/web-platform/tests/web-animations/interfaces/Animation/ready.html new file mode 100644 index 000000000..815fe3da7 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/ready.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.ready</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-ready"> +<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"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + var originalReadyPromise = animation.ready; + var pauseReadyPromise; + + return animation.ready.then(function() { + assert_equals(animation.ready, originalReadyPromise, + 'Ready promise is the same object when playing completes'); + animation.pause(); + assert_not_equals(animation.ready, originalReadyPromise, + 'A new ready promise is created when pausing'); + pauseReadyPromise = animation.ready; + // Wait for the promise to fulfill since if we abort the pause the ready + // promise object is reused. + return animation.ready; + }).then(function() { + animation.play(); + assert_not_equals(animation.ready, pauseReadyPromise, + 'A new ready promise is created when playing'); + }); +}, 'A new ready promise is created when play()/pause() is called'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + return animation.ready.then(function() { + var promiseBeforeCallingPlay = animation.ready; + animation.play(); + assert_equals(animation.ready, promiseBeforeCallingPlay, + 'Ready promise has same object identity after redundant call' + + ' to play()'); + }); +}, 'Redundant calls to play() do not generate new ready promise objects'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + return animation.ready.then(function(resolvedAnimation) { + assert_equals(resolvedAnimation, animation, + 'Object identity of Animation passed to Promise callback' + + ' matches the Animation object owning the Promise'); + }); +}, 'The ready promise is fulfilled with its Animation'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + var retPromise = animation.ready.then(function() { + assert_unreached('ready promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + + animation.cancel(); + + return retPromise; +}, 'ready promise is rejected when a pause-pending animation is cancelled by' + + ' calling cancel()'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + return animation.ready.then(function() { + animation.pause(); + // Set up listeners on pause-pending ready promise + var retPromise = animation.ready.then(function() { + assert_unreached('ready promise was fulfilled'); + }).catch(function(err) { + assert_equals(err.name, 'AbortError', + 'ready promise is rejected with AbortError'); + }); + animation.cancel(); + return retPromise; + }); +}, 'ready promise is rejected when a pause-pending animation is cancelled by' + + ' calling cancel()'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/reverse.html b/testing/web-platform/tests/web-animations/interfaces/Animation/reverse.html new file mode 100644 index 000000000..555226111 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/reverse.html @@ -0,0 +1,158 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.reverse()</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animation-reverse"> +<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"; + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + + // Wait a frame because if currentTime is still 0 when we call + // reverse(), it will throw (per spec). + return animation.ready.then(waitForAnimationFrames(1)).then(function() { + assert_greater_than_equal(animation.currentTime, 0, + 'currentTime expected to be greater than 0, one frame after starting'); + animation.currentTime = 50 * MS_PER_SEC; + var previousPlaybackRate = animation.playbackRate; + animation.reverse(); + assert_equals(animation.playbackRate, -previousPlaybackRate, + 'playbackRate should be inverted'); + }); +}, 'reverse() inverts playbackRate'); + +promise_test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + animation.currentTime = 50 * MS_PER_SEC; + animation.pause(); + + return animation.ready.then(function() { + animation.reverse(); + return animation.ready; + }).then(function() { + assert_equals(animation.playState, 'running', + 'Animation.playState should be "running" after reverse()'); + }); +}, 'reverse() starts to play when pausing animation'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.currentTime = 50 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 50 * MS_PER_SEC, + 'reverse() should not change the currentTime ' + + 'if the currentTime is in the middle of animation duration'); +}, 'reverse() maintains the same currentTime'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.currentTime = 200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'reverse() should start playing from the animation effect end ' + + 'if the playbackRate > 0 and the currentTime > effect end'); +}, 'reverse() when playbackRate > 0 and currentTime > effect end'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + + animation.currentTime = -200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 100 * MS_PER_SEC, + 'reverse() should start playing from the animation effect end ' + + 'if the playbackRate > 0 and the currentTime < 0'); +}, 'reverse() when playbackRate > 0 and currentTime < 0'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.playbackRate = -1; + animation.currentTime = -200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 0, + 'reverse() should start playing from the start of animation time ' + + 'if the playbackRate < 0 and the currentTime < 0'); +}, 'reverse() when playbackRate < 0 and currentTime < 0'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.playbackRate = -1; + animation.currentTime = 200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 0, + 'reverse() should start playing from the start of animation time ' + + 'if the playbackRate < 0 and the currentTime > effect end'); +}, 'reverse() when playbackRate < 0 and currentTime > effect end'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + animation.currentTime = -200 * MS_PER_SEC; + + assert_throws('InvalidStateError', + function () { animation.reverse(); }, + 'reverse() should throw InvalidStateError ' + + 'if the playbackRate > 0 and the currentTime < 0 ' + + 'and the target effect is positive infinity'); +}, 'reverse() when playbackRate > 0 and currentTime < 0 ' + + 'and the target effect end is positive infinity'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, {duration: 100 * MS_PER_SEC, + iterations: Infinity}); + animation.playbackRate = -1; + animation.currentTime = -200 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.currentTime, 0, + 'reverse() should start playing from the start of animation time ' + + 'if the playbackRate < 0 and the currentTime < 0 ' + + 'and the target effect is positive infinity'); +}, 'reverse() when playbackRate < 0 and currentTime < 0 ' + + 'and the target effect end is positive infinity'); + +test(function(t) { + var div = createDiv(t); + var animation = div.animate({}, 100 * MS_PER_SEC); + animation.playbackRate = 0; + animation.currentTime = 50 * MS_PER_SEC; + animation.reverse(); + + assert_equals(animation.playbackRate, 0, + 'reverse() should preserve playbackRate if the playbackRate == 0'); + assert_equals(animation.currentTime, 50 * MS_PER_SEC, + 'reverse() should not affect the currentTime if the playbackRate == 0'); + t.done(); +}, 'reverse() when playbackRate == 0'); + +test(function(t) { + var div = createDiv(t); + var animation = + new Animation(new KeyframeEffect(div, null, 100 * MS_PER_SEC), null); + + assert_throws('InvalidStateError', function() { animation.reverse(); }); +}, 'Reversing an animation without an active timeline throws an ' + + 'InvalidStateError'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/startTime.html b/testing/web-platform/tests/web-animations/interfaces/Animation/startTime.html new file mode 100644 index 000000000..6dfd7cf29 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/startTime.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.startTime tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-animation-starttime"> +<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 animation = new Animation(new KeyframeEffect(createDiv(t), null), + document.timeline); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a newly created (idle) animation is unresolved'); + +test(function(t) { + var animation = new Animation(new KeyframeEffect(createDiv(t), null), + document.timeline); + animation.play(); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a play-pending animation is unresolved'); + +test(function(t) { + var animation = new Animation(new KeyframeEffect(createDiv(t), null), + document.timeline); + animation.pause(); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a pause-pending animation is unresolved'); + +test(function(t) { + var animation = createDiv(t).animate(null); + assert_equals(animation.startTime, null, 'startTime is unresolved'); +}, 'startTime of a play-pending animation created using Element.animate' + + ' shortcut is unresolved'); + +promise_test(function(t) { + var animation = createDiv(t).animate(null, 100 * MS_PER_SEC); + return animation.ready.then(function() { + assert_greater_than(animation.startTime, 0, 'startTime when running'); + }); +}, 'startTime is resolved when running'); + +test(function(t) { + var animation = createDiv(t).animate(null, 100 * MS_PER_SEC); + animation.cancel(); + assert_equals(animation.startTime, null); + assert_equals(animation.currentTime, null); +}, 'startTime and currentTime are unresolved when animation is cancelled'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html new file mode 100644 index 000000000..d6e1cd904 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/delay.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>delay tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-delay"> +<link rel="author" title="Daisuke Akatsuka" href="mailto:daisuke@mozilla-japan.org"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.delay = 100; + assert_equals(anim.effect.timing.delay, 100, 'set delay 100'); + assert_equals(anim.effect.getComputedTiming().delay, 100, + 'getComputedTiming() after set delay 100'); +}, 'set delay 100'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.delay = -100; + assert_equals(anim.effect.timing.delay, -100, 'set delay -100'); + assert_equals(anim.effect.getComputedTiming().delay, -100, + 'getComputedTiming() after set delay -100'); +}, 'set delay -100'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.delay = 100; + assert_equals(anim.effect.getComputedTiming().progress, null); + assert_equals(anim.effect.getComputedTiming().currentIteration, null); +}, 'Test adding a positive delay to an animation without a backwards fill ' + + 'makes it no longer active'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { fill: 'both', + duration: 100 }); + anim.effect.timing.delay = -50; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); +}, 'Test seeking an animation by setting a negative delay'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { fill: 'both', + duration: 100 }); + anim.effect.timing.delay = -100; + assert_equals(anim.effect.getComputedTiming().progress, 1); + assert_equals(anim.effect.getComputedTiming().currentIteration, 0); +}, 'Test finishing an animation using a large negative delay'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html new file mode 100644 index 000000000..c3657f3a0 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/direction.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>direction tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-direction"> +<link rel="author" title="Ryo Kato" href="mailto:foobar094@gmail.com"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + + var directions = ['normal', 'reverse', 'alternate', 'alternate-reverse']; + directions.forEach(function(direction) { + anim.effect.timing.direction = direction; + assert_equals(anim.effect.timing.direction, direction, + 'set direction to ' + direction); + }); +}, 'set direction to a valid keyword'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html new file mode 100644 index 000000000..8e2a1a1b9 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/duration.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>duration tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#dom-animationeffecttiming-duration"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.duration = 123.45; + assert_times_equal(anim.effect.timing.duration, 123.45, + 'set duration 123.45'); + assert_times_equal(anim.effect.getComputedTiming().duration, 123.45, + 'getComputedTiming() after set duration 123.45'); +}, 'set duration 123.45'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.duration = 'auto'; + assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\''); + assert_equals(anim.effect.getComputedTiming().duration, 0, + 'getComputedTiming() after set duration \'auto\''); +}, 'set duration auto'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 'auto' }); + assert_equals(anim.effect.timing.duration, 'auto', 'set duration \'auto\''); + assert_equals(anim.effect.getComputedTiming().duration, 0, + 'getComputedTiming() after set duration \'auto\''); +}, 'set auto duration in animate as object'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.duration = Infinity; + assert_equals(anim.effect.timing.duration, Infinity, 'set duration Infinity'); + assert_equals(anim.effect.getComputedTiming().duration, Infinity, + 'getComputedTiming() after set duration Infinity'); +}, 'set duration Infinity'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, -1); + }); +}, 'set negative duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, -Infinity); + }); +}, 'set negative Infinity duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, NaN); + }); +}, 'set NaN duration in animate using a duration parameter'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: -1 }); + }); +}, 'set negative duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: -Infinity }); + }); +}, 'set negative Infinity duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: NaN }); + }); +}, 'set NaN duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: 'abc' }); + }); +}, 'set abc string duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + assert_throws({ name: 'TypeError' }, function() { + div.animate({ opacity: [ 0, 1 ] }, { duration: '100' }); + }); +}, 'set 100 string duration in animate using an options object'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = -1; + }); +}, 'set negative duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = -Infinity; + }); +}, 'set negative Infinity duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = NaN; + }); +}, 'set NaN duration'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = 'abc'; + }); +}, 'set duration abc'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.duration = '100'; + }); +}, 'set duration string 100'); + + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html new file mode 100644 index 000000000..cedd7e68b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/easing.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>easing tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-easing"> +<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> +<script> +'use strict'; + +function assert_progress(animation, currentTime, easingFunction) { + animation.currentTime = currentTime; + var portion = currentTime / animation.effect.timing.duration; + assert_approx_equals(animation.effect.getComputedTiming().progress, + easingFunction(portion), + 0.01, + 'The progress of the animation should be approximately ' + + easingFunction(portion) + ' at ' + currentTime + 'ms'); +} + +gEffectEasingTests.forEach(function(options) { + test(function(t) { + var target = createDiv(t); + var anim = target.animate([ { opacity: 0 }, { opacity: 1 } ], + { duration: 1000 * MS_PER_SEC, + fill: 'forwards' }); + anim.effect.timing.easing = options.easing; + assert_equals(anim.effect.timing.easing, + options.serialization || options.easing); + + var easing = options.easingFunction; + assert_progress(anim, 0, easing); + assert_progress(anim, 250 * MS_PER_SEC, easing); + assert_progress(anim, 500 * MS_PER_SEC, easing); + assert_progress(anim, 750 * MS_PER_SEC, easing); + assert_progress(anim, 1000 * MS_PER_SEC, easing); + }, options.desc); +}); + +gInvalidEasingTests.forEach(function(options) { + test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC); + assert_throws({ name: 'TypeError' }, + function() { + anim.effect.timing.easing = options.easing; + }); + }, 'Invalid effect easing value test: \'' + options.easing + '\''); +}); + +test(function(t) { + var delay = 1000 * MS_PER_SEC; + + var target = createDiv(t); + var anim = target.animate([ { opacity: 0 }, { opacity: 1 } ], + { duration: 1000 * MS_PER_SEC, + fill: 'both', + delay: delay, + easing: 'steps(2, start)' }); + + anim.effect.timing.easing = 'steps(2, end)'; + assert_equals(anim.effect.getComputedTiming().progress, 0, + 'easing replace to steps(2, end) at before phase'); + + anim.currentTime = delay + 750 * MS_PER_SEC; + assert_equals(anim.effect.getComputedTiming().progress, 0.5, + 'change currentTime to active phase'); + + anim.effect.timing.easing = 'steps(2, start)'; + assert_equals(anim.effect.getComputedTiming().progress, 1, + 'easing replace to steps(2, start) at active phase'); + + anim.currentTime = delay + 1500 * MS_PER_SEC; + anim.effect.timing.easing = 'steps(2, end)'; + assert_equals(anim.effect.getComputedTiming().progress, 1, + 'easing replace to steps(2, end) again at after phase'); +}, 'Change the easing while the animation is running'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html new file mode 100644 index 000000000..40136b45c --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/endDelay.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>endDelay tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#dom-animationeffecttiming-enddelay"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.endDelay = 123.45; + assert_times_equal(anim.effect.timing.endDelay, 123.45, + 'set endDelay 123.45'); + assert_times_equal(anim.effect.getComputedTiming().endDelay, 123.45, + 'getComputedTiming() after set endDelay 123.45'); +}, 'set endDelay 123.45'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.endDelay = -1000; + assert_equals(anim.effect.timing.endDelay, -1000, 'set endDelay -1000'); + assert_equals(anim.effect.getComputedTiming().endDelay, -1000, + 'getComputedTiming() after set endDelay -1000'); +}, 'set endDelay -1000'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({name: "TypeError"}, function() { + anim.effect.timing.endDelay = Infinity; + }, 'we can not assign Infinity to timing.endDelay'); +}, 'set endDelay Infinity'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({name: "TypeError"}, function() { + anim.effect.timing.endDelay = -Infinity; + }, 'we can not assign negative Infinity to timing.endDelay'); +}, 'set endDelay negative Infinity'); + +async_test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 100000, endDelay: 50000 }); + anim.onfinish = t.step_func(function(event) { + assert_unreached('onfinish event should not be fired'); + }); + + anim.ready.then(function() { + anim.currentTime = 100000; + return waitForAnimationFrames(2); + }).then(t.step_func(function() { + t.done(); + })); +}, 'onfinish event is not fired duration endDelay'); + +async_test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 100000, endDelay: 30000 }); + anim.ready.then(function() { + anim.currentTime = 110000; // during endDelay + anim.onfinish = t.step_func(function(event) { + assert_unreached('onfinish event should not be fired during endDelay'); + }); + return waitForAnimationFrames(2); + }).then(t.step_func(function() { + anim.onfinish = t.step_func(function(event) { + t.done(); + }); + anim.currentTime = 130000; // after endTime + })); +}, 'onfinish event is fired currentTime is after endTime'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html new file mode 100644 index 000000000..21e523b73 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/fill.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>fill tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-fill"> +<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'; + +["none", "forwards", "backwards", "both", ].forEach(function(fill){ + test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + anim.effect.timing.fill = fill; + assert_equals(anim.effect.timing.fill, fill, 'set fill ' + fill); + assert_equals(anim.effect.getComputedTiming().fill, fill, 'getComputedTiming() after set fill ' + fill); + }, 'set fill ' + fill); +}); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getAnimations.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getAnimations.html new file mode 100644 index 000000000..d96192c9d --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getAnimations.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Element.getAnimations tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#animationeffecttiming"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.finish(); + assert_equals(div.getAnimations().length, 0, 'animation finished'); + anim.effect.timing.duration += 100000; + assert_equals(div.getAnimations()[0], anim, 'set duration 102000'); + anim.effect.timing.duration = 0; + assert_equals(div.getAnimations().length, 0, 'set duration 0'); + anim.effect.timing.duration = 'auto'; + assert_equals(div.getAnimations().length, 0, 'set duration \'auto\''); +}, 'when duration is changed'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + + anim.effect.timing.endDelay = -3000; + assert_equals(div.getAnimations().length, 0, + 'set negative endDelay so as endTime is less than currentTime'); + anim.effect.timing.endDelay = 1000; + assert_equals(div.getAnimations()[0], anim, + 'set positive endDelay so as endTime is more than currentTime'); + + anim.effect.timing.duration = 1000; + anim.currentTime = 1500; + assert_equals(div.getAnimations().length, 0, + 'set currentTime less than endTime'); + anim.effect.timing.endDelay = -500; + anim.currentTime = 400; + assert_equals(div.getAnimations()[0], anim, + 'set currentTime less than endTime when endDelay is negative value'); + anim.currentTime = 500; + assert_equals(div.getAnimations().length, 0, + 'set currentTime same as endTime when endDelay is negative value'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, + 'set currentTime same as duration when endDelay is negative value'); +}, 'when endDelay is changed'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.finish(); + assert_equals(div.getAnimations().length, 0, 'animation finished'); + anim.effect.timing.iterations = 10; + assert_equals(div.getAnimations()[0], anim, 'set iterations 10'); + anim.effect.timing.iterations = 0; + assert_equals(div.getAnimations().length, 0, 'set iterations 0'); + anim.effect.timing.iterations = Infinity; + assert_equals(div.getAnimations().length, 1, 'set iterations Infinity'); +}, 'when iterations is changed'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 1000, delay: 500, endDelay: -500 }); + assert_equals(div.getAnimations()[0], anim, 'when currentTime 0'); + anim.currentTime = 500; + assert_equals(div.getAnimations()[0], anim, 'set currentTime 500'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, 'set currentTime 1000'); +}, 'when currentTime changed in duration:1000, delay: 500, endDelay: -500'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 1000, delay: -500, endDelay: -500 }); + assert_equals(div.getAnimations().length, 0, 'when currentTime 0'); + anim.currentTime = 500; + assert_equals(div.getAnimations().length, 0, 'set currentTime 500'); + anim.currentTime = 1000; + assert_equals(div.getAnimations().length, 0, 'set currentTime 1000'); +}, 'when currentTime changed in duration:1000, delay: -500, endDelay: -500'); + + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html new file mode 100644 index 000000000..d6503a431 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/getComputedStyle.html @@ -0,0 +1,172 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>getComputedStyle tests</title> +<link rel="help" href="http://w3c.github.io/web-animations/#animationeffecttiming"> +<link rel="author" title="Ryo Motozawa" href="mailto:motozawa@mozilla-japan.org"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100000); + anim.finish(); + assert_equals(getComputedStyle(div).opacity, '1', 'animation finished'); + anim.effect.timing.duration *= 2; + assert_equals(getComputedStyle(div).opacity, '0.5', 'set double duration'); + anim.effect.timing.duration = 0; + assert_equals(getComputedStyle(div).opacity, '1', 'set duration 0'); + anim.effect.timing.duration = 'auto'; + assert_equals(getComputedStyle(div).opacity, '1', 'set duration \'auto\''); +}, 'changed duration immediately updates its computed styles'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100000); + anim.finish(); + assert_equals(getComputedStyle(div).opacity, '1', 'animation finished'); + anim.effect.timing.iterations = 2; + assert_equals(getComputedStyle(div).opacity, '0', 'set 2 iterations'); + anim.effect.timing.iterations = 0; + assert_equals(getComputedStyle(div).opacity, '1', 'set iterations 0'); + anim.effect.timing.iterations = Infinity; + assert_equals(getComputedStyle(div).opacity, '0', 'set iterations Infinity'); +}, 'changed iterations immediately updates its computed styles'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, endDelay: 1000, fill: 'none' }); + + anim.currentTime = 9000; + assert_equals(getComputedStyle(div).opacity, '0.1', + 'set currentTime during duration'); + + anim.currentTime = 10900; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime during endDelay'); + + anim.currentTime = 11100; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime after endDelay'); +}, 'change currentTime when fill is none and endDelay is positive'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, + endDelay: 1000, + fill: 'forwards' }); + anim.currentTime = 5000; + assert_equals(getComputedStyle(div).opacity, '0.5', + 'set currentTime during duration'); + + anim.currentTime = 9999; + assert_equals(getComputedStyle(div).opacity, '0.0001', + 'set currentTime just a little before duration'); + + anim.currentTime = 10900; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime during endDelay'); + + anim.currentTime = 11100; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime after endDelay'); +}, 'change currentTime when fill forwards and endDelay is positive'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, endDelay: -5000, fill: 'none' }); + + anim.currentTime = 1000; + assert_equals(getComputedStyle(div).opacity, '0.9', + 'set currentTime before endTime'); + + anim.currentTime = 10000; + assert_equals(getComputedStyle(div).opacity, '1', + 'set currentTime after endTime'); +}, 'change currentTime when fill none and endDelay is negative'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 1, 0 ] }, + { duration: 10000, + endDelay: -5000, + fill: 'forwards' }); + + anim.currentTime = 1000; + assert_equals(getComputedStyle(div).opacity, '0.9', + 'set currentTime before endTime'); + + anim.currentTime = 5000; + assert_equals(getComputedStyle(div).opacity, '0.5', + 'set currentTime same as endTime'); + + anim.currentTime = 10000; + assert_equals(getComputedStyle(div).opacity, '0', + 'set currentTime after endTime'); +}, 'change currentTime when fill forwards and endDelay is negative'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { duration: 10000, + direction: 'normal' }); + + anim.currentTime = 7000; + anim.effect.timing.direction = 'reverse'; + + assert_equals(getComputedStyle(div).opacity, '0.3', + 'change direction from "normal" to "reverse"'); +}, 'change direction from "normal" to "reverse"'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterations: 2, + duration: 10000, + direction: 'normal' }); + + anim.currentTime = 17000; + anim.effect.timing.direction = 'alternate'; + + assert_equals(getComputedStyle(div).opacity, '0.3', + 'change direction from "normal" to "alternate"'); + }, 'change direction from "normal" to "alternate"'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterations: 2, + duration: 10000, + direction: 'normal' }); + + anim.currentTime = 17000; + anim.effect.timing.direction = 'alternate-reverse'; + + assert_equals(getComputedStyle(div).opacity, '0.7', + 'change direction from "normal" to "alternate-reverse"'); +}, 'change direction from "normal" to "alternate-reverse"'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { fill: 'backwards', + duration: 10000, + direction: 'normal' }); + + // test for a flip of value at the currentTime = 0 + anim.effect.timing.direction = 'reverse'; + + assert_equals(getComputedStyle(div).opacity, '1', + 'change direction from "normal" to "reverse" ' + + 'at the starting point'); +}, 'change direction from "normal" to "reverse"'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html new file mode 100644 index 000000000..0273fd12b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterationStart.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>iterationStart tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-iterationstart"> +<link rel="author" title="Daisuke Akatsuka" href="mailto:daisuke@mozilla-japan.org"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: 0.2, + iterations: 1, + fill: 'both', + duration: 100, + delay: 1 }); + anim.effect.timing.iterationStart = 2.5; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); + assert_equals(anim.effect.getComputedTiming().currentIteration, 2); +}, 'Test that changing the iterationStart affects computed timing ' + + 'when backwards-filling'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: 0.2, + iterations: 1, + fill: 'both', + duration: 100, + delay: 0 }); + anim.effect.timing.iterationStart = 2.5; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); + assert_equals(anim.effect.getComputedTiming().currentIteration, 2); +}, 'Test that changing the iterationStart affects computed timing ' + + 'during the active phase'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: 0.2, + iterations: 1, + fill: 'both', + duration: 100, + delay: 0 }); + anim.finish(); + anim.effect.timing.iterationStart = 2.5; + assert_equals(anim.effect.getComputedTiming().progress, 0.5); + assert_equals(anim.effect.getComputedTiming().currentIteration, 3); +}, 'Test that changing the iterationStart affects computed timing ' + + 'when forwards-filling'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 100); + assert_throws({ name: 'TypeError' }, + function() { + anim.effect.timing.iterationStart = -1; + }); + assert_throws({ name: 'TypeError' }, + function() { + div.animate({ opacity: [ 0, 1 ] }, + { iterationStart: -1 }); + }); +}, 'Test invalid iterationStart value'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html new file mode 100644 index 000000000..1ba41028b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationEffectTiming/iterations.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>iterations tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-animationeffecttiming-iterations"> +<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 div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.iterations = 2; + assert_equals(anim.effect.timing.iterations, 2, 'set duration 2'); + assert_equals(anim.effect.getComputedTiming().iterations, 2, + 'getComputedTiming() after set iterations 2'); +}, 'set iterations 2'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + anim.effect.timing.iterations = Infinity; + assert_equals(anim.effect.timing.iterations, Infinity, 'set duration Infinity'); + assert_equals(anim.effect.getComputedTiming().iterations, Infinity, + 'getComputedTiming() after set iterations Infinity'); +}, 'set iterations Infinity'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.iterations = -1; + }); +}, 'set negative iterations'); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.iterations = -Infinity; + }); +}, 'set negative infinity iterations '); + +test(function(t) { + var div = createDiv(t); + var anim = div.animate({ opacity: [ 0, 1 ] }, 2000); + assert_throws({ name: 'TypeError' }, function() { + anim.effect.timing.iterations = NaN; + }); +}, 'set NaN iterations'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/document-timeline.html b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/document-timeline.html new file mode 100644 index 000000000..0f782f50a --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/document-timeline.html @@ -0,0 +1,59 @@ +<!doctype html> +<meta charset=utf-8> +<title>Default document timeline tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#the-documents-default-timeline"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<iframe width="10" height="10" id="iframe"></iframe> +<script> +'use strict'; + +test(function() { + assert_equals(document.timeline, document.timeline, + 'document.timeline returns the same object every time'); + var iframe = document.getElementById('iframe'); + assert_not_equals(document.timeline, iframe.contentDocument.timeline, + 'document.timeline returns a different object for each document'); + assert_not_equals(iframe.contentDocument.timeline, null, + 'document.timeline on an iframe is not null'); +}, 'document.timeline identity tests'); + +promise_test(function(t) { + assert_true(document.timeline.currentTime > 0, + 'document.timeline.currentTime is positive'); + // document.timeline.currentTime should be set even before document + // load fires. We expect this code to be run before document load and hence + // the above assertion is sufficient. + // If the following assertion fails, this test needs to be redesigned. + assert_true(document.readyState !== 'complete', + 'Test is running prior to document load'); + + // Test that the document timeline's current time is measured from + // navigationStart. + // + // We can't just compare document.timeline.currentTime to + // window.performance.now() because currentTime is only updated on a sample + // so we use requestAnimationFrame instead. + return window.requestAnimationFrame(t.step_func(function(rafTime) { + assert_equals(document.timeline.currentTime, rafTime, + 'document.timeline.currentTime matches' + + ' requestAnimationFrame time'); + })); +}, 'document.timeline.currentTime value tests'); + +promise_test(function(t) { + var valueAtStart = document.timeline.currentTime; + var timeAtStart = window.performance.now(); + while (window.performance.now() - timeAtStart < 100) { + // Wait 100ms + } + assert_equals(document.timeline.currentTime, valueAtStart, + 'document.timeline.currentTime does not change within a script block'); + return window.requestAnimationFrame(t.step_func(function() { + assert_true(document.timeline.currentTime > valueAtStart, + 'document.timeline.currentTime increases between script blocks'); + })); +}, 'document.timeline.currentTime liveness tests'); + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/idlharness.html b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/idlharness.html new file mode 100644 index 000000000..29c891407 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/AnimationTimeline/idlharness.html @@ -0,0 +1,36 @@ +<!doctype html> +<meta charset=utf-8> +<title>Web Animations API: DocumentTimeline tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/WebIDLParser.js"></script> +<script src="/resources/idlharness.js"></script> +<div id="log"></div> +<script type="text/plain" id="AnimationTimeline-IDL"> +interface AnimationTimeline { + readonly attribute double? currentTime; +}; +</script> +<script type="text/plain" id="DocumentTimeline-IDL"> +dictionary DocumentTimelineOptions { + DOMHighResTimeStamp originTime = 0; +}; +[Constructor (optional DocumentTimelineOptions options)] +interface DocumentTimeline : AnimationTimeline { +}; +</script> +<script> +'use strict'; + +var idlArray; +test(function() { + idlArray = new IdlArray(); + idlArray.add_untested_idls( + document.getElementById('AnimationTimeline-IDL').textContent); + idlArray.add_idls( + document.getElementById('DocumentTimeline-IDL').textContent); + idlArray.add_objects( { DocumentTimeline: ['document.timeline'] } ); +}); +idlArray.test(); + +</script> diff --git a/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html b/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html new file mode 100644 index 000000000..e87751c04 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Document/getAnimations.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>document.getAnimations tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#dom-document-getanimations"> +<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 gKeyFrames = { 'marginLeft': ['100px', '200px'] }; + +test(function(t) { + assert_equals(document.getAnimations().length, 0, + 'getAnimations returns an empty sequence for a document ' + + 'with no animations'); +}, 'Test document.getAnimations for non-animated content'); + +test(function(t) { + var div = createDiv(t); + var anim1 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + var anim2 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + assert_equals(document.getAnimations().length, 2, + 'getAnimation returns running animations'); + + anim1.finish(); + anim2.finish(); + assert_equals(document.getAnimations().length, 0, + 'getAnimation only returns running animations'); +}, 'Test document.getAnimations for script-generated animations') + +test(function(t) { + var div = createDiv(t); + var anim1 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + var anim2 = div.animate(gKeyFrames, 100 * MS_PER_SEC); + assert_array_equals(document.getAnimations(), + [ anim1, anim2 ], + 'getAnimations() returns running animations'); +}, 'Test the order of document.getAnimations with script generated animations') + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, gKeyFrames, 100 * MS_PER_SEC); + var anim = new Animation(effect, document.timeline); + anim.play(); + + assert_equals(document.getAnimations().length, 0, + 'document.getAnimations() only returns animations targeting ' + + 'elements in this document'); +}, 'Test document.getAnimations with null target'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/DocumentTimeline/constructor.html b/testing/web-platform/tests/web-animations/interfaces/DocumentTimeline/constructor.html new file mode 100644 index 000000000..8968ff4ce --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/DocumentTimeline/constructor.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>DocumentTimeline constructor tests</title> +<link rel="help" href="https://w3c.github.io/web-animations/#the-documenttimeline-interface"> +<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 timeline = new DocumentTimeline(); + + assert_times_equal(timeline.currentTime, document.timeline.currentTime); +}, 'An origin time of zero is used when none is supplied'); + +test(function(t) { + var timeline = new DocumentTimeline({ originTime: 0 }); + assert_times_equal(timeline.currentTime, document.timeline.currentTime); +}, 'A zero origin time produces a document timeline with a current time ' + + 'identical to the default document timeline'); + +test(function(t) { + var timeline = new DocumentTimeline({ originTime: 10 * MS_PER_SEC }); + + assert_times_equal(timeline.currentTime, + (document.timeline.currentTime - 10 * MS_PER_SEC)); +}, 'A positive origin time makes the document timeline\'s current time lag ' + + 'behind the default document timeline'); + +test(function(t) { + var timeline = new DocumentTimeline({ originTime: -10 * MS_PER_SEC }); + + assert_times_equal(timeline.currentTime, + (document.timeline.currentTime + 10 * MS_PER_SEC)); +}, 'A negative origin time makes the document timeline\'s current time run ' + + 'ahead of the default document timeline'); + +</script> +</body> 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> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html new file mode 100644 index 000000000..287ffe114 --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/copy-contructor.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly copy constructor tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-keyframeeffectreadonly-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); + var copiedEffect = new KeyframeEffectReadOnly(effect); + assert_equals(copiedEffect.target, effect.target, 'same target'); +}, 'Test copied keyframeEffectReadOnly has the same target'); + +test(function(t) { + var effect = + new KeyframeEffectReadOnly(null, + [ { marginLeft: '0px' }, + { marginLeft: '-20px', easing: 'ease-in', + offset: 0.1 }, + { marginLeft: '100px', easing: 'ease-out' }, + { marginLeft: '50px' } ], + { spacing: 'paced(margin-left)' }); + + var copiedEffect = new KeyframeEffectReadOnly(effect); + var KeyframesA = effect.getKeyframes(); + var KeyframesB = copiedEffect.getKeyframes(); + assert_equals(KeyframesA.length, KeyframesB.length, 'same keyframes length'); + + for (var i = 0; i < KeyframesA.length; ++i) { + assert_equals(KeyframesA[i].offset, KeyframesB[i].offset, + 'Keyframe ' + i + ' has the same offset'); + assert_equals(KeyframesA[i].computedOffset, KeyframesB[i].computedOffset, + 'keyframe ' + i + ' has the same computedOffset'); + assert_equals(KeyframesA[i].easing, KeyframesB[i].easing, + 'keyframe ' + i + ' has the same easing'); + assert_equals(KeyframesA[i].composite, KeyframesB[i].composite, + 'keyframe ' + i + ' has the same composite'); + + assert_true(!!KeyframesA[i].marginLeft, + 'original keyframe ' + i + ' has the valid property value'); + assert_true(!!KeyframesB[i].marginLeft, + 'new keyframe ' + i + ' has the valid property value'); + assert_equals(KeyframesA[i].marginLeft, KeyframesB[i].marginLeft, + 'keyframe ' + i + ' has the same property value pair'); + } +}, 'Test copied keyframeEffectReadOnly has the same keyframes'); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, null, + { spacing: 'paced(margin-left)', + iterationComposite: 'accumulate' }); + + var copiedEffect = new KeyframeEffectReadOnly(effect); + assert_equals(copiedEffect.spacing, effect.spacing, 'same spacing'); + assert_equals(copiedEffect.iterationComposite, effect.iterationComposite, + 'same iterationCompositeOperation'); + assert_equals(copiedEffect.composite, effect.composite, + 'same compositeOperation'); +}, 'Test copied keyframeEffectReadOnly has the same keyframeEffectOptions'); + +test(function(t) { + var effect = new KeyframeEffectReadOnly(null, null, + { duration: 100 * MS_PER_SEC, + delay: -1 * MS_PER_SEC, + endDelay: 2 * MS_PER_SEC, + fill: 'forwards', + iterationStart: 2, + iterations: 20, + easing: 'ease-out', + direction: 'alternate' } ); + + var copiedEffect = new KeyframeEffectReadOnly(effect); + var timingA = effect.timing; + var timingB = copiedEffect.timing; + assert_not_equals(timingA, timingB, 'different timing objects'); + assert_equals(timingA.delay, timingB.delay, 'same delay'); + assert_equals(timingA.endDelay, timingB.endDelay, 'same endDelay'); + assert_equals(timingA.fill, timingB.fill, 'same fill'); + assert_equals(timingA.iterationStart, timingB.iterationStart, + 'same iterationStart'); + assert_equals(timingA.iterations, timingB.iterations, 'same iterations'); + assert_equals(timingA.duration, timingB.duration, 'same duration'); + assert_equals(timingA.direction, timingB.direction, 'same direction'); + assert_equals(timingA.easing, timingB.easing, 'same easing'); +}, 'Test copied keyframeEffectReadOnly has the same timing content'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/spacing.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/spacing.html new file mode 100644 index 000000000..c83d1ebcb --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffectReadOnly/spacing.html @@ -0,0 +1,237 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>KeyframeEffectReadOnly spacing attribute tests</title> +<link rel="help" +href="https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-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) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: '' }); + }); +}, 'Test throwing TypeError if using empty string'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'dist' }); + }); +}, 'Test throwing TypeError if not using the correct keyword'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: ' paced(margin-left)' }); + }); +}, 'Test throwing TypeError if adding leading spaces'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(margin-left) ' }); + }); +}, 'Test throwing TypeError if adding trailing spaces'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced( margin-left)' }); + }); +}, 'Test throwing TypeError if adding leading spaces before the ' + + 'paced property'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(margin-left )' }); + }); +}, 'Test throwing TypeError if adding trailing spaces after the ' + + 'paced property'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced()' }); + }); +}, 'Test throwing TypeError if these is no paced property'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(.margin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(1margin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\\fmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape (FF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\\rmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape (CR)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(\\\nmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'an invalid escape (LF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(- )' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid name-start code point'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\\fmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape (FF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\\rmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape (CR)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(-\\\nmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident started string with ' + + 'a leading minus and an invalid escape (LF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\\fmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape (FF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\\rmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape (CR)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(--\\\nmargin)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid ' + + 'escape (LF)'); + +test(function(t) { + assert_throws(new TypeError, function() { + createDiv(t).animate(null, { spacing: 'paced(margin.left)' }); + }); +}, 'Test throwing TypeError if using a non-ident string with an invalid name ' + + 'code point'); + +test(function(t) { + var anim = createDiv(t).animate(null, { 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, { spacing: 'paced(\\.margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a valid escape (Full stop)'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(\\ margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a valid escape (white space)'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(_margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a valid escape (low line)'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(-_margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a minus and a low line'); + +test(function(t) { + var anim = createDiv(t).animate(null, { spacing: 'paced(-\\.margin)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a unrecognized property ' + + 'which starts with a minus and a valid escape'); + +test(function(t) { + var anim = createDiv(t).animate(null, { 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, { spacing: 'paced(animation)' }); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test falling back to distribute spacing if using a non-animatable ' + + 'shorthand property'); + +test(function(t) { + var anim = createDiv(t).animate(null, + { 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); + assert_equals(anim.effect.spacing, 'distribute', 'spacing mode'); +}, 'Test default value of spacing'); + +test(function(t) { + var anim = createDiv(t).animate(null, { 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, { spacing: 'paced(margin-left)' }); + assert_equals(anim.effect.spacing, 'paced(margin-left)', 'spacing mode'); +}, 'Test spacing value if setting paced'); + +</script> +</body> |