// This script is used to test elements that implement
// nsIDOMXULSelectControlElement. This currently is the following elements:
//   listbox, menulist, radiogroup, richlistbox, tabs
//
// flag behaviours that differ for certain elements
//   allow-other-value - alternate values for the value property may be used
//                       besides those in the list
//   other-value-clears-selection - alternative values for the value property
//                                  clears the selected item
//   selection-required - an item must be selected in the list, unless there
//                        aren't any to select
//   activate-disabled-menuitem - disabled menuitems can be highlighted
//   select-keynav-wraps - key navigation over a selectable list wraps
//   select-extended-keynav - home, end, page up and page down keys work to
//                            navigate over a selectable list
//   keynav-leftright - key navigation is left/right rather than up/down
// The win:, mac: and gtk: or other prefixes may be used for platform specific behaviour
var behaviours = {
  menu: "win:activate-disabled-menuitem activate-disabled-menuitem-mousemove select-keynav-wraps select-extended-keynav",
  menulist: "allow-other-value other-value-clears-selection",
  listbox: "select-extended-keynav",
  richlistbox: "select-extended-keynav",
  radiogroup: "select-keynav-wraps dont-select-disabled allow-other-value",
  tabs: "select-extended-keynav mac:select-keynav-wraps allow-other-value selection-required keynav-leftright",
};

function behaviourContains(tag, behaviour) {
  var platform = "none:";
  if (navigator.platform.includes("Mac")) {
    platform = "mac:";
  } else if (navigator.platform.includes("Win")) {
    platform = "win:";
  } else if (navigator.platform.includes("X")) {
    platform = "gtk:";
  }

  var re = new RegExp(
    "\\s" + platform + behaviour + "\\s|\\s" + behaviour + "\\s"
  );
  return re.test(" " + behaviours[tag] + " ");
}

function test_nsIDOMXULSelectControlElement(element, childtag, testprefix) {
  var testid = testprefix ? testprefix + " " : "";
  testid += element.localName + " nsIDOMXULSelectControlElement ";

  // 'initial' - check if the initial state of the element is correct
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "initial",
    0,
    null,
    -1,
    ""
  );

  test_nsIDOMXULSelectControlElement_init(element, testid);

  // 'appendItem' - check if appendItem works to add a new item
  var firstitem = element.appendItem("First Item", "first");
  is(
    firstitem.localName,
    childtag,
    testid + "appendItem - first item is " + childtag
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "appendItem",
    1,
    null,
    -1,
    ""
  );

  is(firstitem.control, element, testid + "control");

  // 'selectedIndex' - check if an item may be selected
  element.selectedIndex = 0;
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "selectedIndex",
    1,
    firstitem,
    0,
    "first"
  );

  // 'appendItem 2' - check if a second item may be added
  var seconditem = element.appendItem("Second Item", "second");
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "appendItem 2",
    2,
    firstitem,
    0,
    "first"
  );

  // 'selectedItem' - check if the second item may be selected
  element.selectedItem = seconditem;
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "selectedItem",
    2,
    seconditem,
    1,
    "second"
  );

  // 'selectedIndex 2' - check if selectedIndex may be set to -1 to deselect items
  var selectionRequired = behaviourContains(
    element.localName,
    "selection-required"
  );
  element.selectedIndex = -1;
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "selectedIndex 2",
    2,
    selectionRequired ? seconditem : null,
    selectionRequired ? 1 : -1,
    selectionRequired ? "second" : ""
  );

  // 'selectedItem 2' - check if the selectedItem property may be set to null
  element.selectedIndex = 1;
  element.selectedItem = null;
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "selectedItem 2",
    2,
    selectionRequired ? seconditem : null,
    selectionRequired ? 1 : -1,
    selectionRequired ? "second" : ""
  );

  // 'getIndexOfItem' - check if getIndexOfItem returns the right index
  is(
    element.getIndexOfItem(firstitem),
    0,
    testid + "getIndexOfItem - first item at index 0"
  );
  is(
    element.getIndexOfItem(seconditem),
    1,
    testid + "getIndexOfItem - second item at index 1"
  );

  var otheritem = element.ownerDocument.createXULElement(childtag);
  is(
    element.getIndexOfItem(otheritem),
    -1,
    testid + "getIndexOfItem - other item not found"
  );

  // 'getItemAtIndex' - check if getItemAtIndex returns the right item
  is(
    element.getItemAtIndex(0),
    firstitem,
    testid + "getItemAtIndex - index 0 is first item"
  );
  is(
    element.getItemAtIndex(1),
    seconditem,
    testid + "getItemAtIndex - index 0 is second item"
  );
  is(
    element.getItemAtIndex(-1),
    null,
    testid + "getItemAtIndex - index -1 is null"
  );
  is(
    element.getItemAtIndex(2),
    null,
    testid + "getItemAtIndex - index 2 is null"
  );

  // check if setting the value changes the selection
  element.value = "first";
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "set value 1",
    2,
    firstitem,
    0,
    "first"
  );
  element.value = "second";
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "set value 2",
    2,
    seconditem,
    1,
    "second"
  );
  // setting the value attribute to one not in the list doesn't change the selection.
  // The value is only changed for elements which support having a value other than the
  // selection.
  element.value = "other";
  var allowOtherValue = behaviourContains(
    element.localName,
    "allow-other-value"
  );
  var otherValueClearsSelection = behaviourContains(
    element.localName,
    "other-value-clears-selection"
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "set value other",
    2,
    otherValueClearsSelection ? null : seconditem,
    otherValueClearsSelection ? -1 : 1,
    allowOtherValue ? "other" : "second"
  );
  if (allowOtherValue) {
    element.value = "";
  }

  var fourthitem = element.appendItem("Fourth Item", "fourth");
  element.selectedIndex = 0;
  fourthitem.disabled = true;
  element.selectedIndex = 2;
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "selectedIndex disabled",
    3,
    fourthitem,
    2,
    "fourth"
  );

  element.selectedIndex = 0;
  element.selectedItem = fourthitem;
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "selectedItem disabled",
    3,
    fourthitem,
    2,
    "fourth"
  );

  if (element.menupopup) {
    element.menupopup.textContent = "";
  } else {
    element.textContent = "";
  }
}

function test_nsIDOMXULSelectControlElement_init(element, testprefix) {
  var id = element.id;
  element = document.getElementById(id + "-initwithvalue");
  if (element) {
    var seconditem = element.getItemAtIndex(1);
    test_nsIDOMXULSelectControlElement_States(
      element,
      testprefix + " value initialization",
      3,
      seconditem,
      1,
      seconditem.value
    );
  }

  element = document.getElementById(id + "-initwithselected");
  if (element) {
    var thirditem = element.getItemAtIndex(2);
    test_nsIDOMXULSelectControlElement_States(
      element,
      testprefix + " selected initialization",
      3,
      thirditem,
      2,
      thirditem.value
    );
  }
}

function test_nsIDOMXULSelectControlElement_States(
  element,
  testid,
  expectedcount,
  expecteditem,
  expectedindex,
  expectedvalue
) {
  // need an itemCount property here
  var count = element.itemCount;
  is(count, expectedcount, testid + " item count");
  is(element.selectedItem, expecteditem, testid + " selectedItem");
  is(element.selectedIndex, expectedindex, testid + " selectedIndex");
  is(element.value, expectedvalue, testid + " value");
  if (element.selectedItem) {
    is(
      element.selectedItem.selected,
      true,
      testid + " selectedItem marked as selected"
    );
  }
}

/** test_nsIDOMXULSelectControlElement_UI
 *
 * Test the UI aspects of an element which implements nsIDOMXULSelectControlElement
 *
 * Parameters:
 *   element - element to test
 */
function test_nsIDOMXULSelectControlElement_UI(element, testprefix) {
  var testid = testprefix ? testprefix + " " : "";
  testid += element.localName + " nsIDOMXULSelectControlElement UI ";

  if (element.menupopup) {
    element.menupopup.textContent = "";
  } else {
    element.textContent = "";
  }

  var firstitem = element.appendItem("First Item", "first");
  var seconditem = element.appendItem("Second Item", "second");

  // 'mouse select' - check if clicking an item selects it
  synthesizeMouseExpectEvent(
    firstitem,
    2,
    2,
    {},
    element,
    "select",
    testid + "mouse select"
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "mouse select",
    2,
    firstitem,
    0,
    "first"
  );

  synthesizeMouseExpectEvent(
    seconditem,
    2,
    2,
    {},
    element,
    "select",
    testid + "mouse select 2"
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "mouse select 2",
    2,
    seconditem,
    1,
    "second"
  );

  // make sure the element is focused so keyboard navigation will apply
  element.selectedIndex = 1;
  element.focus();

  var navLeftRight = behaviourContains(element.localName, "keynav-leftright");
  var backKey = navLeftRight ? "VK_LEFT" : "VK_UP";
  var forwardKey = navLeftRight ? "VK_RIGHT" : "VK_DOWN";

  // 'key select' - check if keypresses move between items
  synthesizeKeyExpectEvent(backKey, {}, element, "select", testid + "key up");
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "key up",
    2,
    firstitem,
    0,
    "first"
  );

  var keyWrap = behaviourContains(element.localName, "select-keynav-wraps");

  var expectedItem = keyWrap ? seconditem : firstitem;
  var expectedIndex = keyWrap ? 1 : 0;
  var expectedValue = keyWrap ? "second" : "first";
  synthesizeKeyExpectEvent(
    backKey,
    {},
    keyWrap ? element : null,
    "select",
    testid + "key up 2"
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "key up 2",
    2,
    expectedItem,
    expectedIndex,
    expectedValue
  );

  element.selectedIndex = 0;
  synthesizeKeyExpectEvent(
    forwardKey,
    {},
    element,
    "select",
    testid + "key down"
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "key down",
    2,
    seconditem,
    1,
    "second"
  );

  expectedItem = keyWrap ? firstitem : seconditem;
  expectedIndex = keyWrap ? 0 : 1;
  expectedValue = keyWrap ? "first" : "second";
  synthesizeKeyExpectEvent(
    forwardKey,
    {},
    keyWrap ? element : null,
    "select",
    testid + "key down 2"
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "key down 2",
    2,
    expectedItem,
    expectedIndex,
    expectedValue
  );

  var thirditem = element.appendItem("Third Item", "third");
  var fourthitem = element.appendItem("Fourth Item", "fourth");
  if (behaviourContains(element.localName, "select-extended-keynav")) {
    element.appendItem("Fifth Item", "fifth");
    var sixthitem = element.appendItem("Sixth Item", "sixth");

    synthesizeKeyExpectEvent(
      "VK_END",
      {},
      element,
      "select",
      testid + "key end"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key end",
      6,
      sixthitem,
      5,
      "sixth"
    );

    synthesizeKeyExpectEvent(
      "VK_HOME",
      {},
      element,
      "select",
      testid + "key home"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key home",
      6,
      firstitem,
      0,
      "first"
    );

    synthesizeKeyExpectEvent(
      "VK_PAGE_DOWN",
      {},
      element,
      "select",
      testid + "key page down"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key page down",
      6,
      fourthitem,
      3,
      "fourth"
    );
    synthesizeKeyExpectEvent(
      "VK_PAGE_DOWN",
      {},
      element,
      "select",
      testid + "key page down to end"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key page down to end",
      6,
      sixthitem,
      5,
      "sixth"
    );

    synthesizeKeyExpectEvent(
      "VK_PAGE_UP",
      {},
      element,
      "select",
      testid + "key page up"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key page up",
      6,
      thirditem,
      2,
      "third"
    );
    synthesizeKeyExpectEvent(
      "VK_PAGE_UP",
      {},
      element,
      "select",
      testid + "key page up to start"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key page up to start",
      6,
      firstitem,
      0,
      "first"
    );

    element.getItemAtIndex(5).remove();
    element.getItemAtIndex(4).remove();
  }

  // now test whether a disabled item works.
  element.selectedIndex = 0;
  seconditem.disabled = true;

  var dontSelectDisabled = behaviourContains(
    element.localName,
    "dont-select-disabled"
  );

  // 'mouse select' - check if clicking an item selects it
  synthesizeMouseExpectEvent(
    seconditem,
    2,
    2,
    {},
    element,
    dontSelectDisabled ? "!select" : "select",
    testid + "mouse select disabled"
  );
  test_nsIDOMXULSelectControlElement_States(
    element,
    testid + "mouse select disabled",
    4,
    dontSelectDisabled ? firstitem : seconditem,
    dontSelectDisabled ? 0 : 1,
    dontSelectDisabled ? "first" : "second"
  );

  if (dontSelectDisabled) {
    // test whether disabling an item won't allow it to be selected
    synthesizeKeyExpectEvent(
      forwardKey,
      {},
      element,
      "select",
      testid + "key down disabled"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key down disabled",
      4,
      thirditem,
      2,
      "third"
    );

    synthesizeKeyExpectEvent(
      backKey,
      {},
      element,
      "select",
      testid + "key up disabled"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key up disabled",
      4,
      firstitem,
      0,
      "first"
    );

    element.selectedIndex = 2;
    firstitem.disabled = true;

    synthesizeKeyExpectEvent(
      backKey,
      {},
      keyWrap ? element : null,
      "select",
      testid + "key up disabled 2"
    );
    expectedItem = keyWrap ? fourthitem : thirditem;
    expectedIndex = keyWrap ? 3 : 2;
    expectedValue = keyWrap ? "fourth" : "third";
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key up disabled 2",
      4,
      expectedItem,
      expectedIndex,
      expectedValue
    );
  } else {
    // in this case, disabled items should behave the same as non-disabled items.
    element.selectedIndex = 0;
    synthesizeKeyExpectEvent(
      forwardKey,
      {},
      element,
      "select",
      testid + "key down disabled"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key down disabled",
      4,
      seconditem,
      1,
      "second"
    );
    synthesizeKeyExpectEvent(
      forwardKey,
      {},
      element,
      "select",
      testid + "key down disabled again"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key down disabled again",
      4,
      thirditem,
      2,
      "third"
    );

    synthesizeKeyExpectEvent(
      backKey,
      {},
      element,
      "select",
      testid + "key up disabled"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key up disabled",
      4,
      seconditem,
      1,
      "second"
    );
    synthesizeKeyExpectEvent(
      backKey,
      {},
      element,
      "select",
      testid + "key up disabled again"
    );
    test_nsIDOMXULSelectControlElement_States(
      element,
      testid + "key up disabled again",
      4,
      firstitem,
      0,
      "first"
    );
  }
}
