chromium/third_party/blink/web_tests/media/controls/text-track-menu-keyboard-navigation.html

<!DOCTYPE html>
<title>Media Controls: text track menu keyboard navigation</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<body>
</body>
<script>

function testNavigation(root, elements, next, previous) {
  // Internal copy of the array. They are otherwise passed by reference.
  const list = elements.slice(0);

  assert_equals(root.activeElement, list[0], 'should start with the first element active');

  list.forEach((element, index) => {
    if (element == list[0])
      return;

    next();

    assert_equals(root.activeElement, element, 'going to the next element should move focus: ' + index);
    assert_equals(
        window.getComputedStyle(element).getPropertyValue('background-color'),
        'rgb(224, 224, 224)', 'the focused element should be properly styled');
  });

  // Next action after reaching the end is a no-op.
  next();
  assert_equals(root.activeElement, list.pop(), 'next from the end is a no-op');
  // pop() will remove the last element which will allow the next iteration to
  // start from n-1.

  list.reverse().forEach((element, index) => {
    previous();

    assert_equals(root.activeElement, element, 'going to the previous element should move focus: ' + index);
    assert_equals(
        window.getComputedStyle(element).getPropertyValue('background-color'),
        'rgb(224, 224, 224)', 'the focused element should be properly styled');
  });

  // Previous element after reaching the beginning is a no-op.
  previous();
  assert_equals(root.activeElement, list[list.length - 1], 'previous from the beginning is a no-op');
}

async_test(t => {
  assert_true('internals' in window);
  assert_true('eventSender' in window);

  const video = document.createElement('video');
  video.controls = true;
  video.src = '../content/test.ogv';
  internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);

  [ '../track/captions-webvtt/captions-fast.vtt',
    '../track/captions-webvtt/captions-rtl.vtt' ].forEach(source => {
    const track = document.createElement('track');
    track.src = source;
    track.kind = 'captions';
    video.appendChild(track);
  });

  assert_equals(video.textTracks.length, 2);

  document.body.appendChild(video);
  enableTestMode(video);

  video.addEventListener('loadedmetadata', t.step_func(() => {
    assert_true(isVisible(overflowButton(video)));

    openOverflowAndClickButton(video, captionsOverflowItem(video), t.step_func_done(() => {
      const menu = textTrackMenu(video);
      assert_true(isVisible(menu));
      assert_equals(internals.shadowRoot(video).activeElement,
                    menu.firstElementChild, 'the original active element should be the first element');

      const elements = [];
      for (let i = 0; i < menu.children.length; i++)
        elements.push(menu.children[i]);

      testNavigation(internals.shadowRoot(video), elements,
                     () => { eventSender.keyDown('ArrowDown'); },
                     () => { eventSender.keyDown('ArrowUp'); });

      testNavigation(internals.shadowRoot(video), elements,
                     () => { eventSender.keyDown('Tab'); },
                     () => { eventSender.keyDown('Tab', [ 'shiftKey' ]); });

      // Navigating with Shift + Arrow Keys should also work.
      testNavigation(internals.shadowRoot(video), elements,
                     () => { eventSender.keyDown('ArrowDown', [ 'shiftKey' ]); },
                     () => { eventSender.keyDown('ArrowUp', [ 'shiftKey' ]); });

      // Closing.
      eventSender.keyDown('Escape');
      assert_false(isVisible(menu));
      assert_equals(internals.shadowRoot(video).activeElement,
                    overflowButton(video));
    }));
  }), { once: true });
});
</script>