diff options
Diffstat (limited to 'testing/web-platform/tests/scroll-animations/scroll-timelines/finish-animation.html')
-rw-r--r-- | testing/web-platform/tests/scroll-animations/scroll-timelines/finish-animation.html | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/testing/web-platform/tests/scroll-animations/scroll-timelines/finish-animation.html b/testing/web-platform/tests/scroll-animations/scroll-timelines/finish-animation.html new file mode 100644 index 0000000000..fd768651b4 --- /dev/null +++ b/testing/web-platform/tests/scroll-animations/scroll-timelines/finish-animation.html @@ -0,0 +1,449 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Finishing an animation</title> +<link rel="help" + href="https://drafts.csswg.org/web-animations/#finishing-an-animation-section"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/web-animations/testcommon.js"></script> +<script src="testcommon.js"></script> +<style> +.scroller { + overflow: auto; + height: 200px; + width: 100px; + will-change: transform; +} + +.contents { + height: 1000px; + width: 100%; +} +</style> +<body> +<div id="log"></div> +<script> +'use strict'; + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + animation.playbackRate = 0; + + assert_throws_dom('InvalidStateError', () => { + animation.finish(); + }); +}, 'Finishing an animation with a zero playback rate throws'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + animation.finish(); + + assert_percents_equal(animation.currentTime, 100, + 'After finishing, the currentTime should be set to the end of the' + + ' active duration'); +}, 'Finishing an animation seeks to the end time'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + // 1% past effect end + animation.currentTime = CSSNumericValue.parse(animation.effect.getComputedTiming().endTime.value + 1 + "%"); + animation.finish(); + + assert_percents_equal(animation.currentTime, 100, + 'After finishing, the currentTime should be set back to the end of the' + + ' active duration'); +}, 'Finishing an animation with a current time past the effect end jumps' + + ' back to the end'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + scroller.scrollTop = maxScroll; + await animation.finished; + + animation.playbackRate = -1; + animation.finish(); + + assert_percents_equal(animation.currentTime, 0, + 'After finishing a reversed animation the ' + + 'currentTime should be set to zero'); +}, 'Finishing a reversed animation jumps to zero time'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + animation.currentTime = CSSNumericValue.parse("100%"); + await animation.finished; + + animation.playbackRate = -1; + animation.currentTime = CSSNumericValue.parse("-1000%"); + animation.finish(); + + assert_percents_equal(animation.currentTime, 0, + 'After finishing a reversed animation the ' + + 'currentTime should be set back to zero'); +}, 'Finishing a reversed animation with a current time less than zero' + + ' makes it jump back to zero'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + animation.playbackRate = 0.5; + animation.finish(); + + assert_false(animation.pending); + assert_equals(animation.playState, 'finished', + 'The play state of a play-pending animation should become ' + + '"finished"'); + assert_percents_equal(animation.startTime, + animation.timeline.currentTime.value - 100 / 0.5, + 'The start time of a play-pending animation should ' + + 'be set'); +}, 'Finishing an animation while play-pending resolves the pending' + + ' task immediately'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + // Make the scroll timeline inactive. + scroller.style.overflow = 'visible'; + scroller.scrollTop; + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + animation.finish(); + + await animation.finished; + + assert_true(animation.pending); + assert_equals(animation.playState, 'finished', + 'The play state of a play-pending animation should become ' + + '"finished"'); + assert_percents_equal(animation.currentTime, 100, + 'The current time of a play-pending animation should ' + + 'be set to the end of the active duration'); + assert_equals(animation.startTime, null, + 'The start time of a finished play-pending animation should ' + + 'be be unresolved'); +}, 'Finishing an animation attached to inactive timeline while play-pending ' + + 'doesn\'t resolves the pending task'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + let resolvedFinished = false; + animation.finished.then(() => { + resolvedFinished = true; + }); + + await animation.ready; + + animation.finish(); + await Promise.resolve(); + + assert_true(resolvedFinished, 'finished promise should be resolved'); +}, 'Finishing an animation resolves the finished promise synchronously'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + const promise = animation.ready; + let readyResolved = false; + + animation.finish(); + animation.ready.then(() => { readyResolved = true; }); + + const promiseResult = await animation.finished; + await animation.ready; + + assert_equals(promiseResult, animation); + assert_equals(animation.ready, promise); + assert_true(readyResolved); +}, 'A pending ready promise is resolved and not replaced when the animation' + + ' is finished'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + animation.effect.target.remove(); + + const eventWatcher = new EventWatcher(t, animation, 'finish'); + + await animation.ready; + animation.finish(); + + await eventWatcher.wait_for('finish'); + assert_equals(animation.effect.target.parentNode, null, + 'finish event should be fired for the animation on an orphaned element'); +}, 'Finishing an animation fires finish event on orphaned element'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + await animation.ready; + + const originalFinishPromise = animation.finished; + + animation.cancel(); + assert_equals(animation.startTime, null); + assert_equals(animation.currentTime, null); + + const resolvedFinishPromise = animation.finished; + assert_not_equals(originalFinishPromise, resolvedFinishPromise, + 'Canceling an animation should create a new finished promise'); + + animation.finish(); + assert_equals(animation.playState, 'finished', + 'The play state of a canceled animation should become ' + + '"finished"'); + assert_percents_equal(animation.startTime, + animation.timeline.currentTime.value - 100, + 'The start time of a finished animation should be set'); + assert_percents_equal(animation.currentTime, 100, + 'Hold time should be set to end boundary of the ' + + 'animation'); + +}, 'Finishing a canceled animation sets the current and start times'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.25 * maxScroll; + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + + const eventWatcher = new EventWatcher(t, animation, 'finish'); + animation.finish(); + await animation.finished; + const finishEvent = await eventWatcher.wait_for('finish'); + assert_equals(animation.playState, 'finished', + 'Animation is finished.'); + assert_percents_equal(animation.currentTime, 100, + 'The current time is the end of the active duration in finished state.'); + assert_percents_equal(animation.startTime, -75, + 'The start time is calculated to match the current time.'); + assert_percents_equal(finishEvent.currentTime, 100, + 'event.currentTime is the animation current time.'); + assert_percents_equal(finishEvent.timelineTime, 25, + 'event.timelineTime is timeline.currentTime'); +}, 'Finishing idle animation produces correct state and fires finish event.'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + // Make the scroll timeline inactive. + scroller.style.overflow = 'visible'; + await waitForNextFrame(); + assert_equals(animation.timeline.currentTime, null, + 'Sanity check the timeline is inactive.'); + animation.finish(); + assert_equals(animation.playState, 'paused', 'Animation is paused.'); +}, 'Finishing idle animation attached to inactive timeline pauses the ' + + 'animation.'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.25 * maxScroll; + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + await animation.ready; + + const eventWatcher = new EventWatcher(t, animation, 'finish'); + animation.finish(); + await animation.finished; + const finishEvent = await eventWatcher.wait_for('finish'); + assert_equals(animation.playState, 'finished', + 'Animation is finished.'); + assert_percents_equal(animation.currentTime, 100, + 'The current time is the end of active duration in finished state.'); + assert_percents_equal(animation.startTime, -75, + 'The start time is calculated to match animation current time.'); + assert_percents_equal(finishEvent.currentTime, 100, + 'event.currentTime is the animation current time.'); + assert_percents_equal(finishEvent.timelineTime, 25, + 'event.timelineTime is timeline.currentTime'); +}, 'Finishing running animation produces correct state and fires finish event.'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + await animation.ready; + + // Make the scroll timeline inactive. + scroller.style.overflow = 'visible'; + scroller.scrollTop; + await waitForNextFrame(); + assert_equals(animation.timeline.currentTime, null, + 'Sanity check the timeline is inactive.'); + assert_equals(animation.playState, 'running', + 'Sanity check the animation is running.'); + + animation.finish(); + assert_equals(animation.playState, 'paused', 'Animation is paused.'); +}, 'Finishing running animation attached to inactive timeline pauses the ' + + 'animation.'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.pause(); + await animation.ready; + + animation.finish(); + + assert_equals(animation.playState, 'finished', + 'The play state of a paused animation should become ' + + '"finished"'); + assert_percents_equal(animation.startTime, + animation.timeline.currentTime.value - 100, + 'The start time of a paused animation should be set'); +}, 'Finishing a paused animation resolves the start time'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + // Update playbackRate so we can test that the calculated startTime + // respects it + animation.playbackRate = 2; + animation.pause(); + // While animation is still pause-pending call finish() + animation.finish(); + + assert_false(animation.pending); + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished"'); + assert_percents_equal(animation.startTime, + animation.timeline.currentTime.value - 100 / 2, + 'The start time of a pause-pending animation should ' + + 'be set'); +}, 'Finishing a pause-pending animation resolves the pending task' + + ' immediately and update the start time'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + animation.playbackRate = -2; + animation.pause(); + animation.finish(); + + assert_false(animation.pending); + assert_equals(animation.playState, 'finished', + 'The play state of a pause-pending animation should become ' + + '"finished"'); + assert_percents_equal(animation.startTime, + animation.timeline.currentTime, + 'The start time of a pause-pending animation should ' + + 'be set'); +}, 'Finishing a pause-pending animation with negative playback rate' + + ' resolves the pending task immediately'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + await animation.ready; + + 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 finishing an animation its play ' + + 'state should become "finished" immediately'); +}, 'Finishing an animation during an aborted pause makes it finished' + + ' immediately'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + await animation.ready; + + animation.updatePlaybackRate(2); + assert_true(animation.pending); + + animation.finish(); + assert_false(animation.pending); + assert_equals(animation.playbackRate, 2); + assert_percents_equal(animation.currentTime, 100); +}, 'A pending playback rate should be applied immediately when an animation' + + ' is finished'); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + // Wait for new animation frame which allows the timeline to compute new + // current time. + await waitForNextFrame(); + animation.play(); + await animation.ready; + + animation.updatePlaybackRate(0); + + assert_throws_dom('InvalidStateError', () => { + animation.finish(); + }); +}, 'An exception should be thrown if the effective playback rate is zero'); +</script> +</body> |