<!DOCTYPE html>
<title>View timelines and animation attachment ranges</title>
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#animation-range">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="support/testcommon.js"></script>
<style>
  @keyframes anim {
    from { z-index: 0; background-color: skyblue;}
    to { z-index: 100; background-color: coral; }
  }
  #scroller {
    border:  10px solid lightgray;
    overflow-y: scroll;
    width: 200px;
    height: 200px;
  }
  #scroller > div {
    margin: 800px 0px;
    width: 100px;
    height: 100px;
  }
  #target {
    font-size: 10px;
    background-color: green;
    z-index: -1;
  }
</style>
<main id=main>
</main>

<template id=template_without_scope>
  <div id=scroller>
    <div id=target class=timeline></div>
  </div>
</template>

<template id=template_with_scope>
  <div id=scope>
    <div id=target></div>
    <div id=scroller>
      <div class=timeline></div>
    </div>
  </div>
</template>

<script>
  setup(assert_implements_animation_timeline);

  function inflate(t, template) {
    t.add_cleanup(() => main.replaceChildren());
    main.append(template.content.cloneNode(true));
  }
  async function scrollTop(e, value) {
    e.scrollTop = value;
    await waitForNextFrame();
  }
  async function waitForAnimationReady(target) {
    await waitForNextFrame();
    await Promise.all(target.getAnimations().map(x => x.ready));
  }
  async function assertValueAt(scroller, target, args) {
    await waitForAnimationReady(target);
    await scrollTop(scroller, args.scrollTop);
    assert_equals(getComputedStyle(target).zIndex, args.expected.toString());
  }
  function test_animation_range(options, template, desc_suffix) {
    if (template === undefined)
      template = template_without_scope;
    if (desc_suffix === undefined)
      desc_suffix = '';

    promise_test(async (t) => {
      inflate(t, template);
      let scroller = main.querySelector('#scroller');
      let target = main.querySelector('#target');
      let timeline = main.querySelector('.timeline');
      let scope = main.querySelector('#scope');

      if (scope != null) {
        scope.style.timelineScope = '--t1';
      }

      timeline.style.viewTimeline = '--t1';
      target.style.animation = 'anim auto linear';
      target.style.animationTimeline = '--t1';
      target.style.animationRangeStart = options.rangeStart;
      target.style.animationRangeEnd = options.rangeEnd;

      // Accommodates floating point precision errors at the endpoints.
      target.style.animationFillMode = 'both';

      // 0%
      await assertValueAt(scroller, target,
          { scrollTop: options.startOffset, expected: 0 });
      // 50%
      await assertValueAt(scroller, target,
          { scrollTop: (options.startOffset + options.endOffset) / 2, expected: 50 });
      // 100%
      await assertValueAt(scroller, target,
          { scrollTop: options.endOffset, expected: 100 });

      // Test before/after phases (need to clear the fill mode for that).
      target.style.animationFillMode = 'initial';
      await assertValueAt(scroller, target,
          { scrollTop: options.startOffset - 10, expected: -1 });
      await assertValueAt(scroller, target,
          { scrollTop: options.endOffset + 10, expected: -1 });
      // Check 50% again without fill mode.
      await assertValueAt(scroller, target,
          { scrollTop: (options.startOffset + options.endOffset) / 2, expected: 50 });

    }, `Animation with ranges [${options.rangeStart}, ${options.rangeEnd}] ${desc_suffix}`.trim());
  }

  test_animation_range({
    rangeStart: 'initial',
    rangeEnd: 'initial',
    startOffset: 600,
    endOffset: 900
  });

  test_animation_range({
    rangeStart: 'cover 0%',
    rangeEnd: 'cover 100%',
    startOffset: 600,
    endOffset: 900
  });

  test_animation_range({
    rangeStart: 'contain 0%',
    rangeEnd: 'contain 100%',
    startOffset: 700,
    endOffset: 800
  });


  test_animation_range({
    rangeStart: 'entry 0%',
    rangeEnd: 'entry 100%',
    startOffset: 600,
    endOffset: 700
  });

  test_animation_range({
    rangeStart: 'exit 0%',
    rangeEnd: 'exit 100%',
    startOffset: 800,
    endOffset: 900
  });

  test_animation_range({
    rangeStart: 'contain -50%',
    rangeEnd: 'entry 200%',
    startOffset: 650,
    endOffset: 800
  });

  test_animation_range({
    rangeStart: 'entry 0%',
    rangeEnd: 'exit 100%',
    startOffset: 600,
    endOffset: 900
  });

  test_animation_range({
    rangeStart: 'cover 20px',
    rangeEnd: 'cover 100px',
    startOffset: 620,
    endOffset: 700
  });

  test_animation_range({
    rangeStart: 'contain 20px',
    rangeEnd: 'contain 100px',
    startOffset: 720,
    endOffset: 800
  });

  test_animation_range({
    rangeStart: 'entry 20px',
    rangeEnd: 'entry 100px',
    startOffset: 620,
    endOffset: 700
  });

  test_animation_range({
    rangeStart: 'entry-crossing 20px',
    rangeEnd: 'entry-crossing 100px',
    startOffset: 620,
    endOffset: 700
  });

  test_animation_range({
    rangeStart: 'exit 20px',
    rangeEnd: 'exit 80px',
    startOffset: 820,
    endOffset: 880
  });

  test_animation_range({
    rangeStart: 'exit-crossing 20px',
    rangeEnd: 'exit-crossing 80px',
    startOffset: 820,
    endOffset: 880
  });

  test_animation_range({
    rangeStart: 'contain 20px',
    rangeEnd: 'contain calc(100px - 10%)',
    startOffset: 720,
    endOffset: 790
  });

  test_animation_range({
    rangeStart: 'exit 2em',
    rangeEnd: 'exit 8em',
    startOffset: 820,
    endOffset: 880
  });

  // Test animation-range via timeline-scope.
  test_animation_range({
    rangeStart: 'exit 2em',
    rangeEnd: 'exit 8em',
    startOffset: 820,
    endOffset: 880
  }, template_with_scope, '(scoped)');

</script>
