<!DOCTYPE html>
<title>HTMLSelectMenuElement Test: events</title>
<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 slot="button" behavior="button">
    <span behavior="selected-value"></span>
    <button id="selectMenu0-button">selectMenu0-button</button>
  </div>
  <option>one</option>
  <option>two</option>
  <option>three</option>
</selectmenu>

<selectmenu id="selectMenu1">
  <option>one</option>
  <option>
    two
    <button id="selectMenu1-button">selectMenu1-button</button>
  </option>
  <option>three</option>
</selectmenu>

<selectmenu id="selectMenu2">
  <option>one</option>
  <option>two</option>
  <option>three</option>
</selectmenu>

<selectmenu id="selectMenu3">
  <option>same</option>
  <option>same</option>
</selectmenu>

<selectmenu id="selectMenu4">
  <option>one</option>
  <option id="selectMenu4-option2">two</option>
</selectmenu>

<selectmenu id="selectMenu5WithTabIndex" tabindex="1">
  <option>one</option>
  <option>two</option>
</selectmenu>

<input id="input6"/>
<selectmenu id="selectMenu7">
  <button slot="button" behavior="button" id="selectMenu7-button">
    selectMenu7-button
  </button>
  <option>one</option>
  <option>two</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 selectMenu = document.getElementById("selectMenu0");
    const selectMenuButton = document.getElementById("selectMenu0-button");
    assert_false(selectMenu.open);
    const selectMenuButtonPromise = new Promise(async resolve => {
      selectMenuButton.addEventListener("click", (e) => {
        assert_false(selectMenu.open, "Listbox shouldn't have opened yet");
        // PreventDefaulting the event here should prevent UA controller code
        // on the button part from opening the listbox.
        e.preventDefault();
        resolve();
      });
    });

    const selectMenuPromise = new Promise(async resolve => {
      selectMenu.addEventListener("click", (e) => {
        assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectMenuButton click handler");
        assert_false(selectMenu.open, "Listbox shouldn't have opened, because click event was defaultPrevented.");
        resolve();
      });
    });

    await clickOn(selectMenuButton);
    return Promise.all([selectMenuButtonPromise, selectMenuPromise]);
  }, "Button controller code should not run if the click event is preventDefaulted.");

  // See https://w3c.github.io/webdriver/#keyboard-actions
  const KEY_CODE_MAP = {
    'Tab':        '\uE004',
    'Enter':      '\uE007',
    'Space':      '\uE00D',
    'ArrowUp':    '\uE013',
    'ArrowDown':  '\uE015',
  };

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu1");
    const selectMenuButton = document.getElementById("selectMenu1-button");
    await clickOn(selectMenu);
    assert_true(selectMenu.open);
    const selectMenuButtonPromise = new Promise(async resolve => {
      selectMenuButton.addEventListener("click", (e) => {
        assert_true(selectMenu.open, "Listbox shouldn't have closed yet");
        // PreventDefaulting the event here should prevent UA controller code
        // on the listbox part from selecting the option and closing the listbox.
        e.preventDefault();
        resolve();
      });
    });

    const selectMenuPromise = new Promise(async resolve => {
      selectMenu.addEventListener("click", (e) => {
        assert_true(e.defaultPrevented, "Event should have been defaultPrevented by selectMenuButton click handler");
        assert_true(selectMenu.open, "Listbox shouldn't have closed, because keydown event was defaultPrevented.");
        assert_equals(selectMenu.value, "one", "<selectmenu> shouldn't have changed value, because keydown event was defaultPrevented.");
        resolve();
      });
    });

    await clickOn(selectMenuButton);
    return Promise.all([selectMenuButtonPromise, selectMenuPromise]);
  }, "Listbox controller code should not run if the click event is preventDefaulted.");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu2");
    let input_event_count = 0;
    let change_event_count = 0;

    selectMenu.addEventListener("input", (e) => {
      assert_true(e.composed, "input event should be composed");
      assert_equals(input_event_count, 0, "input event should not fire twice");
      assert_equals(change_event_count, 0, "input event should not fire before change");
      input_event_count++;
    });
    selectMenu.addEventListener("change", (e) => {
      assert_false(e.composed, "change event should not be composed");
      assert_equals(input_event_count, 1, "change event should fire after input");
      assert_equals(change_event_count, 0, "change event should not fire twice");
      change_event_count++;
    });

    await clickOn(selectMenu);
    assert_true(selectMenu.open);
    await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
    assert_false(selectMenu.open);
    assert_equals(selectMenu.value, "one");
    assert_equals(input_event_count, 0, "input event shouldn't fire if value wasn't changed");
    assert_equals(change_event_count, 0, "change event shouldn't fire if value wasn't changed");

    await clickOn(selectMenu);
    assert_true(selectMenu.open);
    await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowDown);
    assert_equals(selectMenu.value, "two", "value should change when user switches options with arrow key");
    assert_equals(input_event_count, 1, "input event should fire when user switches options with arrow key");
    assert_equals(change_event_count, 0, "change event shouldn't fire until popover is closed");

    await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
    assert_equals(selectMenu.value, "two");
    assert_equals(input_event_count, 1, "input event should have fired");
    assert_equals(change_event_count, 1, "change event should have fired");
  }, "<selectmenu> should fire input and change events when new option is selected");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu3");
    let input_event_count = 0;
    let change_event_count = 0;

    selectMenu.addEventListener("input", (e) => {
      assert_true(e.composed, "input event should be composed");
      assert_equals(input_event_count, 0, "input event should not fire twice");
      assert_equals(change_event_count, 0, "input event should not fire before change");
      input_event_count++;
    });
    selectMenu.addEventListener("change", (e) => {
      assert_false(e.composed, "change event should not be composed");
      assert_equals(input_event_count, 1, "change event should fire after input");
      assert_equals(change_event_count, 0, "change event should not fire twice");
      change_event_count++;
    });

    await clickOn(selectMenu);
    assert_true(selectMenu.open);
    await test_driver.send_keys(selectMenu, KEY_CODE_MAP.ArrowDown);
    await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
    assert_equals(input_event_count, 1, "input event should have fired");
    assert_equals(change_event_count, 1, "change event should have fired");
  }, "<selectmenu> should fire input and change events even when new selected option has the same value as the old");

  promise_test(async () => {
    const selectMenu = document.getElementById("selectMenu4");
    const selectMenuOption2 = document.getElementById("selectMenu4-option2");
    let input_event_count = 0;
    let change_event_count = 0;

    selectMenu.addEventListener("input", (e) => {
      assert_true(e.composed, "input event should be composed");
      assert_equals(input_event_count, 0, "input event should not fire twice");
      assert_equals(change_event_count, 0, "input event should not fire before change");
      input_event_count++;
    });

    selectMenu.addEventListener("change", (e) => {
      assert_false(e.composed, "change event should not be composed");
      assert_equals(input_event_count, 1, "change event should fire after input");
      assert_equals(change_event_count, 0, "change event should not fire twice");
      change_event_count++;
    });

    await clickOn(selectMenu);
    assert_true(selectMenu.open);
    await clickOn(selectMenuOption2);
    assert_equals(input_event_count, 1, "input event shouldn't fire when selected option didn't change");
    assert_equals(change_event_count, 1, "change event shouldn't fire when selected option didn't change");
  }, "<selectmenu> should fire input and change events when option in listbox is clicked");

  promise_test(async() => {
    const selectMenu = document.getElementById("selectMenu2");
    await test_driver.send_keys(selectMenu, " ");
    assert_true(selectMenu.open, "<Space> should open selectmenu");
    await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
    assert_false(selectMenu.open, "<Enter> should close selectmenu");
  }, "Check that <Space> opens <selectmenu>.");

  promise_test(async() => {
    const selectMenu = document.getElementById("selectMenu5WithTabIndex");
    await test_driver.send_keys(selectMenu, " ");
    assert_true(selectMenu.open, "<Space> should open selectmenu");
    await test_driver.send_keys(selectMenu, KEY_CODE_MAP.Enter);
    assert_false(selectMenu.open, "<Enter> should close selectmenu");
  }, "Check that <Space> opens <selectmenu> when <selectmenu> specifies tabindex");

  promise_test(async() => {
    const input6 = document.getElementById("input6");
    const selectMenu = document.getElementById("selectMenu7");
    const selectMenuButton = document.getElementById("selectMenu7-button")

    var keydown_count = 0;
    selectMenuButton.addEventListener("keydown", (e) => {
      keydown_count++;
    });

    // Focus selectmenu via Tab traversal because focus() does not work when selectmenu
    // has custom slot.
    // TODO(http://crbug.com/1440573) Fix this.
    await test_driver.send_keys(input6, KEY_CODE_MAP.Tab);

    await test_driver.send_keys(selectMenu, "a");
    assert_equals(keydown_count, 1, "button in shadowroot should have observed keydown");
}, "Test that <selectmenu> button slot receives key events.");
</script>
