<!DOCTYPE html>
<meta charset="utf-8">
<title>HTML Test: focus() on shadow host with delegatesFocus</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/shadow-utils.js"></script>

<body>
<div id="host">
  <div id="slottedToSecondSlot" slot="secondSlot">slottedToSecondSlot</div>
  <div id="slottedToFirstSlot" slot="firstSlot">slottedToFirstSlot</div>
</div>
<div id="outside">outside</div>
</body>

<script>
const host = document.getElementById("host");
const slottedToSecondSlot = document.getElementById("slottedToSecondSlot");
const slottedToFirstSlot = document.getElementById("slottedToFirstSlot");
const outside = document.getElementById("outside");

const shadowRoot = host.attachShadow({ mode: "open", delegatesFocus: true });
const aboveSlots = document.createElement("div");
aboveSlots.innerText = "aboveSlots";
const firstSlot = document.createElement("slot");
firstSlot.name = "firstSlot";
const secondSlot = document.createElement("slot");
secondSlot.name = "secondSlot";
const belowSlots = document.createElement("div");
belowSlots.innerText = "belowSlots";
shadowRoot.appendChild(aboveSlots);
shadowRoot.appendChild(firstSlot);
shadowRoot.appendChild(secondSlot);
shadowRoot.appendChild(belowSlots);

const elementsInFlatTreeOrder = [host, aboveSlots, firstSlot,
  slottedToFirstSlot, secondSlot, slottedToSecondSlot, belowSlots, outside];

// Final structure:
// <div #host> (delegatesFocus=true)
//    #shadowRoot
//      <div #aboveSlots>
//      <slot #firstSlot>
//        (slotted) <div #slottedToFirstSlot>
//      <slot #secondSlot>
//        (slotted) <div #slottedToSecondSlot>
//      <div #belowSlots>
// <div #outside>


function setAllTabIndex(value) {
  setTabIndex(elementsInFlatTreeOrder, value);
}

function removeAllTabIndex() {
  removeTabIndex(elementsInFlatTreeOrder);
}

function resetTabIndexAndFocus() {
  removeAllTabIndex();
  resetFocus(document);
  resetFocus(shadowRoot);
}

test(() => {
  resetTabIndexAndFocus();
  setAllTabIndex(0);
  // Structure:
  // <div #host> (delegatesFocus=true) tabindex=0
  //    #shadowRoot
  //      <div #aboveSlots> tabindex=0
  //      <slot #firstSlot> tabindex=0
  //        (slotted) <div #slottedToFirstSlot> tabindex=0
  //      <slot #secondSlot> tabindex=0
  //        (slotted) <div #slottedToSecondSlot> tabindex=0
  //      <div #belowSlots> tabindex=0
  // <div #outside> tabindex=0
  // First focusable = #aboveSlots
  host.focus();
  assert_equals(shadowRoot.activeElement, aboveSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus, all tabindex=0");

test(() => {
  resetTabIndexAndFocus();
  setAllTabIndex(0);
  setTabIndex([host], -1);
  // First focusable = #aboveSlots
  host.focus();
  assert_equals(shadowRoot.activeElement, aboveSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus & tabindex =-1, all other tabindex=0");

test(() => {
  resetTabIndexAndFocus();
  setTabIndex([aboveSlots, slottedToFirstSlot, slottedToSecondSlot, belowSlots], 0);
  // First focusable = #aboveSlots
  host.focus();
  assert_equals(shadowRoot.activeElement, aboveSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus & no tabindex, all other tabindex=0");

test(() => {
  resetTabIndexAndFocus();
  setAllTabIndex(-1);
  setTabIndex([host], 0);
  // First focusable = #aboveSlots
  host.focus();
  assert_equals(shadowRoot.activeElement, aboveSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus & tabindex = 0, all other tabindex=-1");

test(() => {
  resetTabIndexAndFocus();
  removeAllTabIndex();
  // No focusable element under #host in the flat tree.
  host.focus();
  assert_equals(shadowRoot.activeElement, null);
  assert_equals(document.activeElement, document.body);
}, "focus() on host with delegatesFocus, all without tabindex");

test(() => {
  resetTabIndexAndFocus();
  // First focusable = #aboveSlots
  setAllTabIndex(-1);
  host.focus();
  assert_equals(shadowRoot.activeElement, aboveSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus, all tabindex=-1");

test(() => {
  resetTabIndexAndFocus();
  removeAllTabIndex();
  setTabIndex([host, belowSlots], 0);
  // Structure:
  // <div #host> (delegatesFocus=true) tabindex=0
  //    #shadowRoot
  //      <div #aboveSlots>
  //      <slot #firstSlot>
  //        (slotted) <div #slottedToFirstSlot>
  //      <slot #secondSlot>
  //        (slotted) <div #slottedToSecondSlot>
  //      <div #belowSlots> tabindex=0
  // <div #outside>
  // First focusable = #belowSlots
  host.focus();
  assert_equals(shadowRoot.activeElement, belowSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus & tabindex=0, #belowSlots with tabindex=0");

test(() => {
  resetTabIndexAndFocus();
  removeAllTabIndex();
  setTabIndex([host, outside], 0);
  // Structure:
  // <div #host> (delegatesFocus=true) tabindex=0
  //    #shadowRoot
  //      <div #aboveSlots>
  //      <slot #firstSlot>
  //        (slotted) <div #slottedToFirstSlot>
  //      <slot #secondSlot>
  //        (slotted) <div #slottedToSecondSlot>
  //      <div #belowSlots>
  // <div #outside> tabindex=0
  // No focusable element under #host in the flat tree.
  host.focus();
  assert_equals(shadowRoot.activeElement, null);
  assert_equals(document.activeElement, document.body);
}, "focus() on host with delegatesFocus & tabindex=0, #outside with tabindex=0");

test(() => {
  resetTabIndexAndFocus();
  setTabIndex([host, aboveSlots, belowSlots], 0);
  // Structure:
  // <div #host> (delegatesFocus=true) tabindex=0
  //    #shadowRoot
  //      <div #aboveSlots> tabindex=0
  //      <slot #firstSlot>
  //        (slotted) <div #slottedToFirstSlot>
  //      <slot #secondSlot>
  //        (slotted) <div #slottedToSecondSlot>
  //      <div #belowSlots> tabindex=0
  // <div #outside>
  // First focusable = #aboveSlots
  host.focus();
  assert_equals(shadowRoot.activeElement, aboveSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus & tabindex=0, #aboveSlots and #belowSlots with tabindex=0");

test(() => {
  resetTabIndexAndFocus();
  setTabIndex([host, aboveSlots], 0);
  setTabIndex([belowSlots], 1);
  // Structure:
  // <div #host> (delegatesFocus=true) tabindex=0
  //    #shadowRoot
  //      <div #aboveSlots> tabindex=0
  //      <slot #firstSlot>
  //        (slotted) <div #slottedToFirstSlot>
  //      <slot #secondSlot>
  //        (slotted) <div #slottedToSecondSlot>
  //      <div #belowSlots> tabindex=1
  // <div #outside>
  // First focusable = #aboveSlots
  host.focus();
  assert_equals(shadowRoot.activeElement, aboveSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus & tabindex=0, #aboveSlots with tabindex=0 and #belowSlots with tabindex=1");

test(() => {
  resetTabIndexAndFocus();
  setTabIndex([host, slottedToFirstSlot, slottedToSecondSlot, belowSlots], 0);
  // Structure:
  // <div #host> (delegatesFocus=true) tabindex=0
  //    #shadowRoot
  //      <div #aboveSlots>
  //      <slot #firstSlot>
  //        (slotted) <div #slottedToFirstSlot> tabindex=0
  //      <slot #secondSlot>
  //        (slotted) <div #slottedToSecondSlot> tabindex=0
  //      <div #belowSlots> tabindex=0
  // <div #outside>
  // First focusable = #slottedToFirstSlot
  host.focus();
  assert_equals(shadowRoot.activeElement, belowSlots);
  assert_equals(document.activeElement, host);
}, "focus() on host with delegatesFocus & tabindex=0, #slottedToFirstSlot, #slottedToSecondSlot, #belowSlots  with tabindex=0");

test(() => {
  resetTabIndexAndFocus();
  setTabIndex([aboveSlots, belowSlots], 0);
  belowSlots.focus();
  host.focus();
  assert_equals(shadowRoot.activeElement, belowSlots);
}, "focus() on host with delegatesFocus and already-focused non-first shadow descendant");

function createNestedHosts(innerDelegatesFocus) {
  // Structure:
  // <div> outerHost
  //   <input> outerLightChild
  //   #shadowRoot outerShadow delegatesFocus=true
  //     <span> innerHost
  //       #shadowRoot inneShadow delegatesFocus=true/false
  //         <input> innerShadowChild
  //     <input> outerShadowChild
  const outerHost = document.createElement('div');
  const outerLightChild = document.createElement('input');
  outerHost.appendChild(outerLightChild);
  const innerHost = document.createElement('span');
  const outerShadow = outerHost.attachShadow({mode: 'closed', delegatesFocus:true});
  outerShadow.appendChild(innerHost);
  const outerShadowChild = document.createElement('input');
  outerShadow.appendChild(outerShadowChild);

  const innerShadow = innerHost.attachShadow({mode: 'closed', delegatesFocus:innerDelegatesFocus});
  const innerShadowChild = document.createElement('input');
  innerShadow.appendChild(innerShadowChild);

  document.body.insertBefore(outerHost, document.body.firstChild);
  return {outerHost: outerHost,
      outerLightChild: outerLightChild,
      outerShadow: outerShadow,
      outerShadowChild: outerShadowChild,
      innerHost: innerHost,
      innerShadow: innerShadow,
      innerShadowChild: innerShadowChild};
}

test(() => {
  const dom = createNestedHosts(false);
  dom.outerHost.focus();
  assert_equals(document.activeElement, dom.outerHost);
  assert_equals(dom.outerShadow.activeElement, dom.outerShadowChild);
}, 'focus() on host with delegatesFocus with another host with no delegatesFocus and a focusable child');

test(() => {
  const dom = createNestedHosts(true);
  dom.outerHost.focus();
  assert_equals(document.activeElement, dom.outerHost);
  assert_equals(dom.outerShadow.activeElement, dom.innerHost);
  assert_equals(dom.innerShadow.activeElement, dom.innerShadowChild);
}, 'focus() on host with delegatesFocus with another host with delegatesFocus and a focusable child');

test(() => {
  // Structure:
  // <div> host
  //   #shadowRoot root delegatesFocus=true
  //    <slot>
  //      (slotted) <div>
  //        <input>
  //    <input #firstFocusable>
  const host = document.createElement("div");
  const slotted = document.createElement("div");
  slotted.appendChild(document.createElement("input"));
  host.appendChild(slotted);

  const root = host.attachShadow({mode: "open", delegatesFocus: true});

  const firstFocusable = document.createElement("input");
  root.innerHTML = "<slot>";
  root.appendChild(firstFocusable);

  document.body.appendChild(host);

  host.focus();
  assert_equals(document.activeElement, host);
  assert_equals(root.activeElement, firstFocusable);
}, "focus() on host with delegatesFocus and slotted focusable children");
</script>

