<!DOCTYPE html>
<meta charset=utf-8>
<title>Reversing an animation</title>
<link rel="help"
      href="https://drafts.csswg.org/web-animations/#reversing-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: 100px;
  width: 100px;
  will-change: transform;
}

.contents {
  height: 1000px;
  width: 100%;
}
</style>
<body>
<script>
'use strict';

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.reverse();
  animation.currentTime = CSSNumericValue.parse("40%");
  await animation.ready;
  assert_percents_equal(animation.currentTime, 40);
}, 'Setting current time while reverse-pending preserves currentTime');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  await animation.ready;

  animation.currentTime = CSSNumericValue.parse("50%");
  const previousPlaybackRate = animation.playbackRate;
  animation.reverse();
  assert_equals(animation.playbackRate, previousPlaybackRate,
                'Playback rate should not have changed');
  await animation.ready;

  assert_equals(animation.playbackRate, -previousPlaybackRate,
                'Playback rate should be inverted');
}, 'Reversing an animation inverts the playback rate');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  animation.currentTime = CSSNumericValue.parse("40%");
  await animation.ready;
  assert_percents_equal(animation.startTime, -40);
  assert_percents_equal(animation.currentTime, 40);

  animation.reverse();
  await animation.ready;
  assert_percents_equal(animation.startTime, 100);
  assert_percents_equal(animation.currentTime, 100);
}, 'Reversing an animation resets a sticky start time.');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  assert_true(animation.pending,
              'The animation is pending before we call reverse');

  animation.reverse();

  assert_true(animation.pending,
              'The animation is still pending after calling reverse');
}, 'Reversing an animation does not cause it to leave the pending state');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  let readyResolved = false;
  animation.ready.then(() => { readyResolved = true; });

  animation.reverse();

  await Promise.resolve();
  assert_false(readyResolved,
               'ready promise should not have been resolved yet');
}, 'Reversing an animation does not cause it to resolve the ready promise');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  animation.playbackRate = -1;
  animation.reverse();
  await animation.ready;

  assert_percents_equal(animation.currentTime, 0);
}, 'Reversing an animation with a negative playback rate should cause ' +
   'the animation to play in a forward direction');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  animation.playbackRate = 0;
  animation.currentTime = CSSNumericValue.parse("50%");
  animation.reverse();

  await animation.ready;
  assert_equals(animation.playbackRate, 0,
      'reverse() should preserve playbackRate if the playbackRate == 0');
  assert_percents_equal(animation.currentTime, 0,
      'Anchors to range start boundary when playback rate is zero');
}, 'Reversing when when playbackRate == 0 should preserve the playback rate');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  assert_equals(animation.currentTime, null);

  animation.reverse();
  await animation.ready;

  assert_percents_equal(animation.startTime, 100,
                        'animation.startTime should be at its effect end');
  assert_percents_equal(animation.currentTime, 100,
                        'animation.currentTime should be at its effect end');
}, 'Reversing an idle animation aligns startTime with the rangeEnd boundary');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  const scroller = animation.timeline.source;
  // Make the scroll timeline inactive.
  scroller.style.overflow = 'visible';
  scroller.scrollTop;
  await waitForNextFrame();

  assert_throws_dom('InvalidStateError', () => { animation.reverse(); });
}, 'Reversing an animation without an active timeline throws an ' +
   'InvalidStateError');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  animation.currentTime = CSSNumericValue.parse("50%");
  animation.pause();
  await animation.ready;

  animation.reverse();
  assert_equals(animation.playState, 'running',
                'Animation.playState should be "running" after reverse()');
}, 'Reversing an animation plays a pausing animation');

promise_test(async t => {
  const animation = createScrollLinkedAnimation(t);
  animation.play();
  await animation.ready;

  animation.updatePlaybackRate(2);
  animation.reverse();

  await animation.ready;
  assert_equals(animation.playbackRate, -2);
}, 'Reversing should use the negative pending playback rate');
</script>
</body>
