<!DOCTYPE html>
<html lang="en">
<title>HTMLSelectMenuElement Test: part structure</title>
<link rel="author" title="Ionel Popescu" href="mailto:iopopesc@microsoft.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>

<selectmenu id="selectMenu0">
  <div popover slot="listbox" behavior="listbox">
    <option id="selectMenu0-child1">one</option>
    <option id="selectMenu0-child2">two</option>
    <div behavior="option" id="selectMenu0-child3">three</div>
  </div>
  <option id="selectMenu0-child4">four</option>
</selectmenu>

<selectmenu id="selectMenu1">
  <div popover slot="listbox" behavior="listbox" id="selectMenu1-popover">
    <div behavior="button" id="selectMenu1-button">
      Custom button
    </div>
    <option>one</option>
    <option id="selectMenu1-child2">two</option>
  </div>
</selectmenu>

<selectmenu id="selectMenu2">
  <div slot="button" behavior="button" id="selectMenu2-button">
    Custom button
    <div popover behavior="listbox" id="selectMenu2-popover">
      <option>one</option>
      <option id="selectMenu2-child2">two</option>
    </div>
  </div>
  <option>three</option>
  <div>
    This is some text.
    <option id="selectMenu2-child4">four</option>
    More text.
  </div>
</selectmenu>

<selectmenu id="selectMenu3">
  <div slot="button" id="selectMenu3-button-slot">
    <div behavior="button" id="selectMenu3-button0">button0</div>
  </div>
  <option>one</option>
</selectmenu>

<selectmenu id="selectMenu4">
  <div slot="button" behavior="button" id="selectMenu4-button0">button0</div>
  <div slot="listbox" id="selectMenu4-listbox-slot">
    <div popover behavior="listbox" id="selectMenu4-listbox0">
      <option>one</option>
      <option id="selectMenu4-option2">two</option>
    </div>
  </div>
</selectmenu>

<selectmenu id="selectMenu5">
  <div slot="button" id="selectMenu5-button-slot">
    <div behavior="button" id="selectMenu5-button0">button0</div>
    <div behavior="selected-value" id="selectMenu5-selectedValue0"></div>
  </div>
  <option>one</option>
  <option id="selectMenu5-option0">two</option>
</selectmenu>

<!-- No associated JS test -- just don't crash when parsing! -->
<selectmenu id="selectMenu6">
  <div slot="button"></div>
  <div popover slot="listbox" behavior="listbox"></div>
</selectmenu>

<!-- No associated JS test -- just don't crash when parsing! -->
<selectmenu id="selectMenu7">
  <div slot="listbox"></div>
  <div slot="button" behavior="button"></div>
</selectmenu>

<!-- No associated JS test -- just don't crash when parsing! -->
<selectmenu id="selectMenu8">
  <div slot="listbox"></div>
  <option>one</option>
</selectmenu>

<selectmenu id="selectMenu9">
  <div slot="listbox" id="selectMenu9-listbox-slot">
    <div popover behavior="listbox" id="selectMenu9-originalListbox">
      <option>one</option>
      <option id="selectMenu9-option2">two</option>
    </div>
  </div>
</selectmenu>

<selectmenu id="selectMenu10">
  <option slot="button" id="selectMenu10-slottedOption">Test 10</option>
  <option>one</option>
  <option id="selectMenu10-option2">two</option>
</selectmenu>

<selectmenu id="selectMenu11">
  <div popover slot="listbox" behavior="listbox">
    <option>one</option>
  </div>
  <div slot="button" behavior="listbox" id="selectMenu11-button">Test</div>
</selectmenu>

<selectmenu id="selectMenu12">
  <div slot="button" id="selectMenu12-button-slot">
    <div behavior="button" id="selectMenu12-button0">button0</div>
  </div>
  <div slot="listbox" id="selectMenu12-listbox-slot">
    <div popover behavior="listbox" id="selectMenu12-originalListbox">
      <option id="selectMenu12-option1">one</option>
      <option>two</option>
    </div>
  </div>
</selectmenu>

<selectmenu id="selectMenu13">
  <div slot="button" id="selectMenu12-button-slot">
    <div id="selectMenu13-removeContent-button">
      <div behavior="button" id="selectMenu13-button0">button0</div>
      <div behavior="button" id="selectMenu13-button1">button1</div>
    </div>
    <div behavior="button" id="selectMenu13-button2">button2</div>
  </div>
  <div slot="listbox" id="selectMenu13-listbox-slot">
    <div id="selectMenu13-removeContent-listbox">
      <div popover behavior="listbox" id="selectMenu13-originalListbox">
        <option id="selectMenu13-option1">one</option>
        <option id="selectMenu13-option2">two</option>
      </div>
    </div>
    <div popover behavior="listbox" id="selectMenu13-newListbox">
      <option>three</option>
      <option id="selectMenu13-option4">four</option>
    </div>
  </div>
</selectmenu>

<selectmenu id="selectMenu14">
  <div slot="button" behavior="button" id="selectMenu14-button0">button0</div>
  <option>one</option>
  <option id="selectMenu14-option2">two</option>
</selectmenu>

<selectmenu id="selectMenu15">
  <div slot="button" id="selectMenu15-div0"></div>
  <option>one</option>
</selectmenu>

<selectmenu id="selectMenu16">
  <div slot="button">
    <div id="selectMenu16-div0">
      <div behavior="button" id="selectMenu16-button0">button</div>
    </div>
  </div>
  <option>one</option>
</selectmenu>

<script>
  function clickOn(element) {
    const actions = new test_driver.Actions();
    return actions.pointerMove(0, 0, {origin: element})
      .pointerDown({button: actions.ButtonType.LEFT})
      .pointerUp({button: actions.ButtonType.LEFT})
      .send();
  }

  promise_test(async () => {
    const selectMenu0 = document.getElementById("selectMenu0");
    const selectMenu0Child1 = document.getElementById("selectMenu0-child1");
    const selectMenu0Child2 = document.getElementById("selectMenu0-child2");
    const selectMenu0Child3 = document.getElementById("selectMenu0-child3");
    assert_equals(selectMenu0.value, "one");
    await clickOn(selectMenu0);
    await clickOn(selectMenu0Child2);
    assert_equals(selectMenu0.value, "two");

    await clickOn(selectMenu0);
    await clickOn(selectMenu0Child3);
    assert_equals(selectMenu0.value, "two", "Clicking a non-HTMLOptionElement labeled as an option should do nothing");

    await clickOn(selectMenu0Child1);
    assert_equals(selectMenu0.value, "one");
    assert_false(selectMenu0.open);
  }, "HTMLOptionElements (and not other element types) should receive option controller code");


  promise_test(async () => {
    const selectMenu0 = document.getElementById("selectMenu0");
    const selectMenu0Child4 = document.getElementById("selectMenu0-child4");

    assert_equals(selectMenu0.value, "one");
    await clickOn(selectMenu0);
    assert_true(selectMenu0.open);
    selectMenu0Child4.click();
    assert_equals(selectMenu0.value, "one", "Clicking an option outside of the popover should not change the value");
  }, "To receive option part controller code, an option must be a descendant of the listbox part in a flat tree traversal");

  promise_test(async () => {
    const selectMenu1 = document.getElementById("selectMenu1");
    const selectMenu1Popover = document.getElementById("selectMenu1-popover");
    const selectMenu1Button = document.getElementById("selectMenu1-button");
    const selectMenu1Child2 = document.getElementById("selectMenu1-child2");
    assert_false(selectMenu1Popover.matches(':popover-open'));
    selectMenu1Button.click();
    assert_false(selectMenu1Popover.matches(':popover-open'), "Clicking a button part that is a descendant of the listbox part should have no effect");

    assert_equals(selectMenu1.value, "one");
    await clickOn(selectMenu1);
    assert_true(selectMenu1Popover.matches(':popover-open'));
    await clickOn(selectMenu1Child2);
    assert_equals(selectMenu1.value, "two", "Clicking an <option> should change the value");
  }, "To receive button part controller code, an element labeled as a button must not be a descendant of the listbox part in a flat tree traversal");

  promise_test(async () => {
    const selectMenu2 = document.getElementById("selectMenu2");
    const selectMenu2Popover = document.getElementById("selectMenu2-popover");
    const selectMenu2Button = document.getElementById("selectMenu2-button");
    const selectMenu2Child2 = document.getElementById("selectMenu2-child2");
    const selectMenu2Child4 = document.getElementById("selectMenu2-child4");

    assert_false(selectMenu2Popover.matches(':popover-open'));
    await clickOn(selectMenu2Button);
    assert_false(selectMenu2Popover.matches(':popover-open'), "Clicking a button part should not show an invalid listbox part");

    assert_equals(selectMenu2.value, "three");
    await clickOn(selectMenu2Button);
    await clickOn(selectMenu2Child4);
    assert_equals(selectMenu2.value, "four", "Clicking an <option> that is a descendant of a valid listbox part should update the value");
  }, "To receive listbox part controller code, an element labeled as a listbox must not be a descendant of the button part in a flat tree traversal");

  promise_test(async () => {
    const selectMenu3 = document.getElementById("selectMenu3");
    const selectMenu3ButtonSlot = document.getElementById("selectMenu3-button-slot");
    const selectMenu3Button0 = document.getElementById("selectMenu3-button0");

    assert_false(selectMenu3.open);

    let button1 = document.createElement("div");
    button1.innerText = "button1";
    button1.setAttribute("behavior", "button");
    selectMenu3ButtonSlot.appendChild(button1);

    await clickOn(button1);
    assert_false(selectMenu3.open, "A button part should only get controller code if it's first in document order, even if added dynamically");

    await clickOn(selectMenu3Button0);
    assert_true(selectMenu3.open, "A button part should get controller code if it's first in document order");
  }, "Button controller code should be applied in flat tree traversal order regardless of dynamic insertion order");

  promise_test(async () => {
    const selectMenu4 = document.getElementById("selectMenu4");
    const selectMenu4Button0 = document.getElementById("selectMenu4-button0");
    const selectMenu4ListboxSlot = document.getElementById("selectMenu4-listbox-slot");
    const selectMenu4Option2 = document.getElementById("selectMenu4-option2");

    assert_false(selectMenu4.open);

    let listbox2 = document.createElement("div");
    listbox2.innerHTML = `
      <option>three</option>
      <option id="selectMenu4-option4">four</option>
    `;
    listbox2.setAttribute("behavior", "listbox");
    selectMenu4ListboxSlot.appendChild(listbox2);

    await clickOn(selectMenu4Button0);
    assert_true(selectMenu4.open);

    const selectMenu4Option4 = document.getElementById("selectMenu4-option4");
    await clickOn(selectMenu4Option4);
    assert_equals(selectMenu3.value, "one", "An option in a listbox should not get controller code if its listbox isn't first in document order, even if added dynamically");

    await clickOn(selectMenu4Button0);
    assert_true(selectMenu4.open);

    await clickOn(selectMenu4Option2);
    assert_equals(selectMenu4.value, "two", "An option in a listbox should get controller code if its listbox is first in document order, even if another listbox was added dynamically");
}, "Listbox controller code should be applied in flat tree traversal order regardless of dynamic insertion order");

promise_test(async () => {
    const selectMenu5 = document.getElementById("selectMenu5");
    const selectMenu5ButtonSlot = document.getElementById("selectMenu5-button-slot");
    const selectMenu5Button0 = document.getElementById("selectMenu5-button0");
    const selectMenu5SelectedValue0 = document.getElementById("selectMenu5-selectedValue0");

    assert_false(selectMenu3.open);
    assert_equals(selectMenu5SelectedValue0.innerText, "one");

    let selectedValue1 = document.createElement("div");
    selectMenu5ButtonSlot.appendChild(selectedValue1);

    await clickOn(selectMenu5Button0);
    assert_true(selectMenu5.open);

    await clickOn(document.getElementById("selectMenu5-option0"));
    assert_false(selectMenu5.open);
    assert_equals(selectMenu5SelectedValue0.innerText, "two", "first selected-value part in flat tree order should get controller code");
    assert_equals(selectedValue1.innerText, "", "Dynamically inserted selected-value part shouldn't get controller code if it's not first in flat tree order");
  }, "selected-value controller code should be applied in flat tree traversal order regardless of dynamic insertion order");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu9");
    const originalListbox = document.getElementById("selectMenu9-originalListbox");
    const option2 = document.getElementById("selectMenu9-option2");
    assert_equals(selectMenu.value, "one", "Initial value should be the first option");

    let newListbox = document.createElement("div");
    newListbox.setAttribute("popover", "auto");
    newListbox.setAttribute("behavior", "listbox");
    let newOption = document.createElement("option");
    newOption.innerText = "three";
    newListbox.appendChild(newOption);
    let newOption2 = document.createElement("option");
    newOption2.innerText = "four";
    newListbox.appendChild(newOption2);
    originalListbox.parentElement.insertBefore(newListbox, originalListbox);

    await clickOn(selectMenu);
    assert_true(selectMenu.open, "Menu should open when clicked");

    option2.click(); // clickOn doesn't work because the old options are not displayed
    assert_equals(selectMenu.value, "three", "Elements in second popover should no longer be option parts");
    assert_true(selectMenu.open, "Clicking non-part options shouldn't close the popover");

    await clickOn(newOption2);
    assert_false(selectMenu.open);
    assert_equals(selectMenu.value, "four", "New options should get controller code after listbox switch");
    }, "Ensure that option controller code is updated when listbox changes");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu10");
    const selectMenu10SlottedOption = document.getElementById("selectMenu10-slottedOption");

    await clickOn(selectMenu10SlottedOption);
    assert_false(selectMenu.open, "Controller code not applied due to part attribute missing");
    selectMenu10SlottedOption.setAttribute("behavior", "button");
    await clickOn(selectMenu10SlottedOption);
    assert_true(selectMenu.open);

    const option2 = document.getElementById("selectMenu10-option2");
    await clickOn(option2);
    assert_equals(selectMenu.value, "two");
    assert_false(selectMenu.open);

    selectMenu10SlottedOption.slot = "";
    await clickOn(selectMenu);
    assert_true(selectMenu.open, "Default button part should be used");
    await clickOn(selectMenu10SlottedOption);
    assert_equals(selectMenu.value, "Test 10");
  }, "Ensure that controller code is applied after updating the slot attribute");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu11");
    const selectMenu11Button= document.getElementById("selectMenu11-button");

    await clickOn(selectMenu11Button);
    assert_false(selectMenu.open, "Controller code not applied due to part attribute not being button");
    selectMenu11Button.remove();

    await clickOn(selectMenu);
    assert_true(selectMenu.open, "Default button part should be used");
  }, "Ensure that controller code is applied when slot and part attributes are different");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu12");
    const originalListbox = document.getElementById("selectMenu12-originalListbox");
    assert_equals(selectMenu.value, "one", "Initial value should be the first option");

    const selectMenuButtonSlot = document.getElementById("selectMenu12-button-slot");
    const selectMenuButton0 = document.getElementById("selectMenu12-button0");
    const selectMenuOption1 = document.getElementById("selectMenu12-option1");

    assert_false(selectMenu.open);
    let button1 = document.createElement("div");
    button1.innerText = "button1";
    button1.setAttribute("behavior", "button");
    selectMenuButtonSlot.insertBefore(button1, selectMenuButton0);
    button1.click();
    assert_true(selectMenu.open, "Controller code should be applied to the new first button in document order");
    await clickOn(selectMenuOption1);
    assert_false(selectMenu.open);
    selectMenuButton0.click();
    assert_false(selectMenu.open);

    let button2 = document.createElement("div");
    button2.innerText = "button2";
    selectMenuButtonSlot.insertBefore(button2, button1);
    button2.click();
    assert_false(selectMenu.open, "Controller code should not be applied to button2 since it doesn't have behavior attribute set");
    button2.setAttribute("behavior", "button");
    button2.click();
    assert_true(selectMenu.open, "Controller code should be applied to the new button part");
    await clickOn(selectMenuOption1);
    assert_false(selectMenu.open);

    let newListbox = document.createElement("div");
    newListbox.setAttribute("popover", "auto");
    newListbox.setAttribute("behavior", "listbox");
    let newOption = document.createElement("option");
    newOption.innerText = "three";
    newListbox.appendChild(newOption);
    let newOption2 = document.createElement("option");
    newOption2.innerText = "four";
    newListbox.appendChild(newOption2);
    originalListbox.parentElement.insertBefore(newListbox, originalListbox);
    assert_equals(selectMenu.value, "three", "New value should be the first option");

    newListbox.innerHTML = "<option>five</option><option>six</option>";
    assert_equals(selectMenu.value, "five", "New value should be the first option");

    selectMenu.innerHTML = "<option>seven</option><option id='selectMenu12-option2'>eight</option>";
    assert_equals(selectMenu.value, "seven", "New value should be the first option");
    const selectMenuOption2 = document.getElementById("selectMenu12-option2");
    await clickOn(selectMenu);
    assert_true(selectMenu.open);
    await clickOn(selectMenuOption2);
    assert_equals(selectMenu.value, "eight", "Controller code should be applied to new options");

    selectMenuOption2.slot = "button";
    assert_equals(selectMenu.value, "seven", "Previous selected option should become invalid");
  }, "Ensure that controller code is synchronously applied");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu13");
    assert_equals(selectMenu.value, "one");

    const selectMenuButton0 = document.getElementById("selectMenu13-button0");
    const selectMenuButton1 = document.getElementById("selectMenu13-button1");
    selectMenuButton1.click();
    assert_false(selectMenu.open);
    selectMenuButton0.click();
    assert_true(selectMenu.open, "First button should receive controller code");
    await clickOn(document.getElementById("selectMenu13-option2"));
    assert_equals(selectMenu.value, "two");
    let divButtonToRemove = document.getElementById("selectMenu13-removeContent-button");
    divButtonToRemove.innerHTML = "";
    selectMenuButton0.click();
    assert_false(selectMenu.open, "The first button is invalid");
    const selectMenuButton2 = document.getElementById("selectMenu13-button2");
    selectMenuButton2.click();
    assert_true(selectMenu.open, "The button part should be updated")
    await clickOn(document.getElementById("selectMenu13-option1"));
    assert_equals(selectMenu.value, "one");

    const selectMenuOption4 = document.getElementById("selectMenu13-option4");
    selectMenuOption4.click();
    assert_equals(selectMenu.value, "one");
    let divListboxToRemove = document.getElementById("selectMenu13-removeContent-listbox");
    divListboxToRemove.innerHTML = "";
    assert_equals(selectMenu.value, "three", "The listbox part should be updated");
    selectMenuOption4.click();
    assert_equals(selectMenu.value, "four", "Controller code should be applied to the new options");

    let selectMenuNewListbox = document.getElementById("selectMenu13-newListbox");
    selectMenuNewListbox.innerHTML = "";
    assert_equals(selectMenu.value, "");
    selectMenuOption4.click();
    assert_equals(selectMenu.value, "");
  }, "Controller code should be updated when nested parts are removed");

  promise_test(async () => {
    let selectMenu = document.getElementById("selectMenu14");
    assert_equals(selectMenu.value, "one");
    const selectMenuButton0 = document.getElementById("selectMenu14-button0");
    const selectMenuOption2 = document.getElementById("selectMenu14-option2");

    selectMenuButton0.click();
    assert_true(selectMenu.open);
    await clickOn(selectMenuOption2);
    assert_equals(selectMenu.value, "two");

    document.body.removeChild(selectMenu);
    selectMenu.removeChild(selectMenuOption2);
    assert_equals(selectMenu.value, "one");
    let newOption = document.createElement("option");
    newOption.innerText = "three";
    selectMenu.appendChild(newOption);
    newOption.click();
    assert_equals(selectMenu.value, "three", "New option should receive controller code");

    let doc = document.implementation.createHTMLDocument('');
    let selectMenu1 = doc.createElement('selectmenu');
    let firstOption = doc.createElement('option');
    firstOption.innerText = 'one';
    let secondOption = doc.createElement('option');
    secondOption.innerText = 'two';
    selectMenu1.appendChild(firstOption);
    selectMenu1.appendChild(secondOption);
    assert_equals(selectMenu1.value, "one");
    secondOption.click();
    assert_equals(selectMenu1.value, "two");
    document.body.appendChild(selectMenu1);
    selectMenu1.removeChild(secondOption);
    assert_equals(selectMenu1.value, "one");
  }, "Moving a selectmenu between documents should keep controller code active");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu15");
    const selectMenuButtonContainer = document.getElementById("selectMenu15-div0");

    const outerDiv = document.createElement("div");
    const button = document.createElement("input");
    button.type = button.value = "button";
    button.setAttribute("behavior", "button");
    outerDiv.appendChild(button);
    selectMenuButtonContainer.appendChild(outerDiv);

    await clickOn(selectMenu);
    assert_true(selectMenu.open, "New button should receive controller code");
  }, "New parts should be detected even when in the subtree of an inserted node");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu16");
    const selectMenuButtonContainer = document.getElementById("selectMenu16-div0");
    const selectMenuButton = document.getElementById("selectMenu16-button0");

    selectMenuButtonContainer.remove();

    selectMenuButton.click();
    assert_false(selectMenu.open, "Removed button should no longer have controller code");
  }, "Part removals should be detected even when in the subtree of a removed node");
</script>
