Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  tree_shared.js   Sprache: JAVA

 
// This file expects the following globals to be defined at various times.
/* globals getCustomTreeViewCellInfo */

// This files relies on these specific Chrome/XBL globals
/* globals TreeColumns, TreeColumn */

var columns_simpletree = [
  { name: "name", label: "Name", key: true, properties: "one two" },
  { name: "address", label: "Address" },
];

var columns_hiertree = [
  {
    name: "name",
    label: "Name",
    primary: true,
    key: true,
    properties: "one two",
  },
  { name: "address", label: "Address" },
  { name: "planet", label: "Planet" },
  { name: "gender", label: "Gender", cycler: true },
];

// XXXndeakin still to add some tests for:
//   cycler columns, checkbox cells

// this test function expects a tree to have 8 rows in it when it isn't
// expanded. The tree should only display four rows at a time. If editable,
// the cell at row 1 and column 0 must be editable, and the cell at row 2 and
// column 1 must not be editable.
async function testtag_tree(
  treeid,
  treerowinfoid,
  seltype,
  columnstype,
  testid
) {
  // Stop keystrokes that aren't handled by the tree from leaking out and
  // scrolling the main Mochitests window!
  function preventDefault(event) {
    event.preventDefault();
  }
  document.addEventListener("keypress", preventDefault);

  var multiple = seltype == "multiple";

  var tree = document.getElementById(treeid);
  var treerowinfo = document.getElementById(treerowinfoid);
  var rowInfo;
  if (testid == "tree view") {
    rowInfo = getCustomTreeViewCellInfo();
  } else {
    rowInfo = convertDOMtoTreeRowInfo(treerowinfo, 0, { value: -1 });
  }
  var columnInfo =
    columnstype == "simple" ? columns_simpletree : columns_hiertree;

  is(tree.selType, seltype == "multiple" ? "" : seltype, testid + " seltype");

  // note: the functions below should be in this order due to changes made in later tests

  await testtag_tree_treecolpicker(tree, columnInfo, testid);
  testtag_tree_columns(tree, columnInfo, testid);
  testtag_tree_TreeSelection(tree, testid, multiple);
  testtag_tree_TreeSelection_UI(tree, testid, multiple);
  testtag_tree_TreeView(tree, testid, rowInfo);

  is(tree.editable, false"tree should not be editable");
  // currently, the editable flag means that tree editing cannot be invoked
  // by the user. However, editing can still be started with a script.
  is(tree.editingRow, -1, testid + " initial editingRow");
  is(tree.editingColumn, null, testid + " initial editingColumn");

  testtag_tree_UI_editing(tree, testid, rowInfo);

  is(
    tree.editable,
    false,
    "tree should not be editable after testtag_tree_UI_editing"
  );
  // currently, the editable flag means that tree editing cannot be invoked
  // by the user. However, editing can still be started with a script.
  is(tree.editingRow, -1, testid + " initial editingRow (continued)");
  is(tree.editingColumn, null, testid + " initial editingColumn (continued)");

  var ecolumn = tree.columns[0];
  ok(
    !tree.startEditing(1, ecolumn),
    "non-editable trees shouldn't start editing"
  );
  is(
    tree.editingRow,
    -1,
    testid + " failed startEditing shouldn't set editingRow"
  );
  is(
    tree.editingColumn,
    null,
    testid + " failed startEditing shouldn't set editingColumn"
  );

  tree.editable = true;

  ok(tree.startEditing(1, ecolumn), "startEditing should have returned true");
  is(tree.editingRow, 1, testid + " startEditing editingRow");
  is(tree.editingColumn, ecolumn, testid + " startEditing editingColumn");
  is(
    tree.getAttribute("editing"),
    "true",
    testid + " startEditing editing attribute"
  );

  tree.stopEditing(true);
  is(tree.editingRow, -1, testid + " stopEditing editingRow");
  is(tree.editingColumn, null, testid + " stopEditing editingColumn");
  is(
    tree.hasAttribute("editing"),
    false,
    testid + " stopEditing editing attribute"
  );

  tree.startEditing(-1, ecolumn);
  is(
    tree.editingRow == -1 && tree.editingColumn == null,
    true,
    testid + " startEditing -1 editingRow"
  );
  tree.startEditing(15, ecolumn);
  is(
    tree.editingRow == -1 && tree.editingColumn == null,
    true,
    testid + " startEditing 15 editingRow"
  );
  tree.startEditing(1, null);
  is(
    tree.editingRow == -1 && tree.editingColumn == null,
    true,
    testid + " startEditing null column editingRow"
  );
  tree.startEditing(2, tree.columns[1]);
  is(
    tree.editingRow == -1 && tree.editingColumn == null,
    true,
    testid + " startEditing non editable cell editingRow"
  );

  tree.startEditing(1, ecolumn);
  var inputField = tree.inputField;
  is(inputField.localName, "input", testid + "inputField");
  inputField.value = "Changed Value";
  tree.stopEditing(true);
  is(
    tree.view.getCellText(1, ecolumn),
    "Changed Value",
    testid + "edit cell accept"
  );

  // this cell can be edited, but stopEditing(false) means don't accept the change.
  tree.startEditing(1, ecolumn);
  inputField.value = "Second Value";
  tree.stopEditing(false);
  is(
    tree.view.getCellText(1, ecolumn),
    "Changed Value",
    testid + "edit cell no accept"
  );

  tree.editable = false;

  // do the sorting tests last as it will cause the rows to rearrange
  // skip them for the custom tree view
  if (testid != "tree view") {
    testtag_tree_TreeView_rows_sort(tree, testid, rowInfo);
  }

  testtag_tree_wheel(tree);

  document.removeEventListener("keypress", preventDefault);

  SimpleTest.finish();
}

async function testtag_tree_treecolpicker(tree, expectedColumns, testid) {
  testid += " ";

  async function showAndHideTreecolpicker() {
    let treecolpicker = tree.querySelector("treecolpicker");
    let treecolpickerMenupopup = treecolpicker.querySelector("menupopup");
    await new Promise(resolve => {
      treecolpickerMenupopup.addEventListener("popupshown", resolve, {
        once: true,
      });
      treecolpicker.querySelector("button").click();
    });
    let menuitems = treecolpicker.querySelectorAll("menuitem");
    // Ignore the last "Restore Column Order" menu in the count:
    is(
      menuitems.length - 1,
      expectedColumns.length,
      testid + "Same number of columns"
    );
    for (var c = 0; c < expectedColumns.length; c++) {
      is(
        menuitems[c].textContent,
        expectedColumns[c].label,
        testid + "treecolpicker menu matches"
      );
      ok(
        !menuitems[c].querySelector("label").hidden,
        testid + "label not hidden"
      );
    }
    await new Promise(resolve => {
      treecolpickerMenupopup.addEventListener("popuphidden", resolve, {
        once: true,
      });
      treecolpickerMenupopup.hidePopup();
    });
  }

  // Regression test for Bug 1549931 (menuitem content being hidden upon second open)
  await showAndHideTreecolpicker();
  await showAndHideTreecolpicker();
}

function testtag_tree_columns(tree, expectedColumns, testid) {
  testid += " ";

  var columns = tree.columns;

  is(
    TreeColumns.isInstance(columns),
    true,
    testid + "columns is a TreeColumns"
  );
  is(columns.count, expectedColumns.length, testid + "TreeColumns count");
  is(columns.length, expectedColumns.length, testid + "TreeColumns length");

  var treecols = tree.getElementsByTagName("treecols")[0];
  var treecol = treecols.getElementsByTagName("treecol");

  var x = 0;
  var primary = null,
    sorted = null,
    key = null;
  for (var c = 0; c < expectedColumns.length; c++) {
    var adjtestid = testid + " column " + c + " ";
    var column = columns[c];
    var expectedColumn = expectedColumns[c];
    is(columns.getColumnAt(c), column, adjtestid + "getColumnAt");
    is(
      columns.getNamedColumn(expectedColumn.name),
      column,
      adjtestid + "getNamedColumn"
    );
    is(columns.getColumnFor(treecol[c]), column, adjtestid + "getColumnFor");
    if (expectedColumn.primary) {
      primary = column;
    }
    if (expectedColumn.sorted) {
      sorted = column;
    }
    if (expectedColumn.key) {
      key = column;
    }

    // XXXndeakin on Windows and Linux, some columns are one pixel to the
    // left of where they should be. Could just be a rounding issue.
    var adj = 1;
    is(
      column.x + adj >= x,
      true,
      adjtestid +
        "position is after last column " +
        column.x +
        "," +
        column.width +
        "," +
        x
    );
    is(column.width > 0, true, adjtestid + "width is greater than 0");
    x = column.x + column.width;

    // now check the TreeColumn properties
    is(TreeColumn.isInstance(column), true, adjtestid + "is a TreeColumn");
    is(column.element, treecol[c], adjtestid + "element is treecol");
    is(column.columns, columns, adjtestid + "columns is TreeColumns");
    is(column.id, expectedColumn.name, adjtestid + "name");
    is(column.index, c, adjtestid + "index");
    is(column.primary, primary == column, adjtestid + "column is primary");

    is(
      column.cycler,
      "cycler" in expectedColumn && expectedColumn.cycler,
      adjtestid + "column is cycler"
    );
    is(
      column.editable,
      "editable" in expectedColumn && expectedColumn.editable,
      adjtestid + "column is editable"
    );

    is(
      column.type,
      "type" in expectedColumn ? expectedColumn.type : 1,
      adjtestid + "type"
    );

    is(
      column.getPrevious(),
      c > 0 ? columns[c - 1] : null,
      adjtestid + "getPrevious"
    );
    is(
      column.getNext(),
      c < columns.length - 1 ? columns[c + 1] : null,
      adjtestid + "getNext"
    );

    // check the view's getColumnProperties method
    var properties = tree.view.getColumnProperties(column);
    var expectedProperties = expectedColumn.properties;
    is(properties, expectedProperties || "", adjtestid + "getColumnProperties");
  }

  is(columns.getFirstColumn(), columns[0], testid + "getFirstColumn");
  is(
    columns.getLastColumn(),
    columns[columns.length - 1],
    testid + "getLastColumn"
  );
  is(columns.getPrimaryColumn(), primary, testid + "getPrimaryColumn");
  is(columns.getSortedColumn(), sorted, testid + "getSortedColumn");
  is(columns.getKeyColumn(), key, testid + "getKeyColumn");

  is(columns.getColumnAt(-1), null, testid + "getColumnAt under");
  is(columns.getColumnAt(columns.length), null, testid + "getColumnAt over");
  is(columns.getNamedColumn(""), null, testid + "getNamedColumn null");
  is(
    columns.getNamedColumn("unknown"),
    null,
    testid + "getNamedColumn unknown"
  );
  is(columns.getColumnFor(null), null, testid + "getColumnFor null");
  is(columns.getColumnFor(tree), null, testid + "getColumnFor other");
}

function testtag_tree_TreeSelection(tree, testid, multiple) {
  testid += " selection ";

  var selection = tree.view.selection;
  is(
    selection instanceof Ci.nsITreeSelection,
    true,
    testid + "selection is a TreeSelection"
  );
  is(selection.single, !multiple, testid + "single");

  testtag_tree_TreeSelection_State(tree, testid + "initial", -1, []);
  is(selection.shiftSelectPivot, -1, testid + "initial shiftSelectPivot");

  selection.currentIndex = 2;
  testtag_tree_TreeSelection_State(tree, testid + "set currentIndex", 2, []);
  tree.currentIndex = 3;
  testtag_tree_TreeSelection_State(
    tree,
    testid + "set tree.currentIndex",
    3,
    []
  );

  // test the select() method, which should deselect all rows and select
  // a single row
  selection.select(1);
  testtag_tree_TreeSelection_State(tree, testid + "select 1", 1, [1]);
  selection.select(3);
  testtag_tree_TreeSelection_State(tree, testid + "select 2", 3, [3]);
  selection.select(3);
  testtag_tree_TreeSelection_State(tree, testid + "select same", 3, [3]);

  selection.currentIndex = 1;
  testtag_tree_TreeSelection_State(
    tree,
    testid + "set currentIndex with single selection",
    1,
    [3]
  );

  tree.currentIndex = 2;
  testtag_tree_TreeSelection_State(
    tree,
    testid + "set tree.currentIndex with single selection",
    2,
    [3]
  );

  // check the toggleSelect method. In single selection mode, it only toggles on when
  // there isn't currently a selection.
  selection.toggleSelect(2);
  testtag_tree_TreeSelection_State(
    tree,
    testid + "toggleSelect 1",
    2,
    multiple ? [2, 3] : [3]
  );
  selection.toggleSelect(2);
  selection.toggleSelect(3);
  testtag_tree_TreeSelection_State(tree, testid + "toggleSelect 2", 3, []);

  // the current index doesn't change after a selectAll, so it should still be set to 1
  // selectAll has no effect on single selection trees
  selection.currentIndex = 1;
  selection.selectAll();
  testtag_tree_TreeSelection_State(
    tree,
    testid + "selectAll 1",
    1,
    multiple ? [0, 1, 2, 3, 4, 5, 6, 7] : []
  );
  selection.toggleSelect(2);
  testtag_tree_TreeSelection_State(
    tree,
    testid + "toggleSelect after selectAll",
    2,
    multiple ? [0, 1, 3, 4, 5, 6, 7] : [2]
  );
  selection.clearSelection();
  testtag_tree_TreeSelection_State(tree, testid + "clearSelection", 2, []);
  selection.toggleSelect(3);
  selection.toggleSelect(1);
  if (multiple) {
    selection.selectAll();
    testtag_tree_TreeSelection_State(
      tree,
      testid + "selectAll 2",
      1,
      [0, 1, 2, 3, 4, 5, 6, 7]
    );
  }
  selection.currentIndex = 2;
  selection.clearSelection();
  testtag_tree_TreeSelection_State(
    tree,
    testid + "clearSelection after selectAll",
    2,
    []
  );

  is(selection.shiftSelectPivot, -1, testid + "shiftSelectPivot set to -1");

  // rangedSelect and clearRange set the currentIndex to the endIndex. The
  // shiftSelectPivot property will be set to startIndex.
  selection.rangedSelect(1, 3, false);
  testtag_tree_TreeSelection_State(
    tree,
    testid + "rangedSelect no augment",
    multiple ? 3 : 2,
    multiple ? [1, 2, 3] : []
  );
  is(
    selection.shiftSelectPivot,
    multiple ? 1 : -1,
    testid + "shiftSelectPivot after rangedSelect no augment"
  );
  if (multiple) {
    selection.select(1);
    selection.rangedSelect(0, 2, true);
    testtag_tree_TreeSelection_State(
      tree,
      testid + "rangedSelect augment",
      2,
      [0, 1, 2]
    );
    is(
      selection.shiftSelectPivot,
      0,
      testid + "shiftSelectPivot after rangedSelect augment"
    );

    selection.clearRange(1, 3);
    testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment", 3, [
      0,
    ]);

    // check that rangedSelect can take a start value higher than end
    selection.rangedSelect(3, 1, false);
    testtag_tree_TreeSelection_State(
      tree,
      testid + "rangedSelect reverse",
      1,
      [1, 2, 3]
    );
    is(
      selection.shiftSelectPivot,
      3,
      testid + "shiftSelectPivot after rangedSelect reverse"
    );

    // check that setting the current index doesn't change the selection
    selection.currentIndex = 0;
    testtag_tree_TreeSelection_State(
      tree,
      testid + "currentIndex with range selection",
      0,
      [1, 2, 3]
    );
  }

  // both values of rangedSelect may be the same
  selection.rangedSelect(2, 2, false);
  testtag_tree_TreeSelection_State(tree, testid + "rangedSelect one row", 2, [
    2,
  ]);
  is(
    selection.shiftSelectPivot,
    2,
    testid + "shiftSelectPivot after selecting one row"
  );

  if (multiple) {
    selection.rangedSelect(2, 3, true);

    // a start index of -1 means from the last point
    selection.rangedSelect(-1, 0, true);
    testtag_tree_TreeSelection_State(
      tree,
      testid + "rangedSelect -1 existing selection",
      0,
      [0, 1, 2, 3]
    );
    is(
      selection.shiftSelectPivot,
      2,
      testid + "shiftSelectPivot after -1 existing selection"
    );

    selection.currentIndex = 2;
    selection.rangedSelect(-1, 0, false);
    testtag_tree_TreeSelection_State(
      tree,
      testid + "rangedSelect -1 from currentIndex",
      0,
      [0, 1, 2]
    );
    is(
      selection.shiftSelectPivot,
      2,
      testid + "shiftSelectPivot -1 from currentIndex"
    );
  }

  // XXXndeakin need to test out of range values but these don't work properly
  /*
  selection.select(-1);
  testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment -1", -1, []);

  selection.select(8);
  testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment 8", 3, [0]);
*/

}

function testtag_tree_TreeSelection_UI(tree, testid, multiple) {
  testid += " selection UI ";

  var selection = tree.view.selection;
  selection.clearSelection();
  selection.currentIndex = 0;
  tree.focus();

  var keydownFired = 0;
  var keypressFired = 0;
  function keydownListener() {
    keydownFired++;
  }
  function keypressListener() {
    keypressFired++;
  }

  // check that cursor up and down keys navigate up and down
  // select event fires after a delay so don't expect it. The reason it fires after a delay
  // is so that cursor navigation allows quicking skimming over a set of items without
  // actually firing events in-between, improving performance. The select event will only
  // be fired on the row where the cursor stops.
  window.addEventListener("keydown", keydownListener);
  window.addEventListener("keypress", keypressListener);

  synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select""key down");
  testtag_tree_TreeSelection_State(tree, testid + "key down", 1, [1], 0);

  synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select""key up");
  testtag_tree_TreeSelection_State(tree, testid + "key up", 0, [0], 0);

  synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select""key up at start");
  testtag_tree_TreeSelection_State(tree, testid + "key up at start", 0, [0], 0);

  // pressing down while the last row is selected should not fire a select event,
  // as the selection won't have changed. Also the view is not scrolled in this case.
  selection.select(7);
  synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select""key down at end");
  testtag_tree_TreeSelection_State(tree, testid + "key down at end", 7, [7], 0);

  // pressing keys while at the edge of the visible rows should scroll the list
  tree.scrollToRow(4);
  selection.select(4);
  synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select""key up with scroll");
  is(tree.getFirstVisibleRow(), 3, testid + "key up with scroll");

  tree.scrollToRow(0);
  selection.select(3);
  synthesizeKeyExpectEvent(
    "VK_DOWN",
    {},
    tree,
    "!select",
    "key down with scroll"
  );
  is(tree.getFirstVisibleRow(), 1, testid + "key down with scroll");

  // accel key and cursor movement adjust currentIndex but should not change
  // the selection. In single selection mode, the selection will not change,
  // but instead will just scroll up or down a line
  tree.scrollToRow(0);
  selection.select(1);
  synthesizeKeyExpectEvent(
    "VK_DOWN",
    { accelKey: true },
    tree,
    "!select",
    "key down with accel"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key down with accel",
    multiple ? 2 : 1,
    [1]
  );
  if (!multiple) {
    is(tree.getFirstVisibleRow(), 1, testid + "key down with accel and scroll");
  }

  tree.scrollToRow(4);
  selection.select(4);
  synthesizeKeyExpectEvent(
    "VK_UP",
    { accelKey: true },
    tree,
    "!select",
    "key up with accel"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key up with accel",
    multiple ? 3 : 4,
    [4]
  );
  if (!multiple) {
    is(tree.getFirstVisibleRow(), 3, testid + "key up with accel and scroll");
  }

  // do this three times, one for each state of pageUpOrDownMovesSelection,
  // and then once with the accel key pressed
  for (let t = 0; t < 3; t++) {
    let testidmod = "";
    if (t == 2) {
      testidmod = " with accel";
    } else if (t == 1) {
      testidmod = " rev";
    }
    var keymod = t == 2 ? { accelKey: true } : {};

    var moveselection = tree.pageUpOrDownMovesSelection;
    if (t == 2) {
      moveselection = !moveselection;
    }

    tree.scrollToRow(4);
    selection.currentIndex = 6;
    selection.select(6);
    var expected = moveselection ? 4 : 6;
    synthesizeKeyExpectEvent(
      "VK_PAGE_UP",
      keymod,
      tree,
      "!select",
      "key page up"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key page up" + testidmod,
      expected,
      [expected],
      moveselection ? 4 : 0
    );

    expected = moveselection ? 0 : 6;
    synthesizeKeyExpectEvent(
      "VK_PAGE_UP",
      keymod,
      tree,
      "!select",
      "key page up again"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key page up again" + testidmod,
      expected,
      [expected],
      0
    );

    expected = moveselection ? 0 : 6;
    synthesizeKeyExpectEvent(
      "VK_PAGE_UP",
      keymod,
      tree,
      "!select",
      "key page up at start"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key page up at start" + testidmod,
      expected,
      [expected],
      0
    );

    tree.scrollToRow(0);
    selection.currentIndex = 1;
    selection.select(1);
    expected = moveselection ? 3 : 1;
    synthesizeKeyExpectEvent(
      "VK_PAGE_DOWN",
      keymod,
      tree,
      "!select",
      "key page down"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key page down" + testidmod,
      expected,
      [expected],
      moveselection ? 0 : 4
    );

    expected = moveselection ? 7 : 1;
    synthesizeKeyExpectEvent(
      "VK_PAGE_DOWN",
      keymod,
      tree,
      "!select",
      "key page down again"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key page down again" + testidmod,
      expected,
      [expected],
      4
    );

    expected = moveselection ? 7 : 1;
    synthesizeKeyExpectEvent(
      "VK_PAGE_DOWN",
      keymod,
      tree,
      "!select",
      "key page down at start"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key page down at start" + testidmod,
      expected,
      [expected],
      4
    );

    if (t < 2) {
      tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection;
    }
  }

  tree.scrollToRow(4);
  selection.select(6);
  synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select""key home");
  testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0);

  tree.scrollToRow(0);
  selection.select(1);
  synthesizeKeyExpectEvent("VK_END", {}, tree, "!select""key end");
  testtag_tree_TreeSelection_State(tree, testid + "key end", 7, [7], 4);

  // in single selection mode, the selection doesn't change in this case
  tree.scrollToRow(4);
  selection.select(6);
  synthesizeKeyExpectEvent(
    "VK_HOME",
    { accelKey: true },
    tree,
    "!select",
    "key home with accel"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key home with accel",
    multiple ? 0 : 6,
    [6],
    0
  );

  tree.scrollToRow(0);
  selection.select(1);
  synthesizeKeyExpectEvent(
    "VK_END",
    { accelKey: true },
    tree,
    "!select",
    "key end with accel"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key end with accel",
    multiple ? 7 : 1,
    [1],
    4
  );

  // next, test cursor navigation with selection. Here the select event will be fired
  selection.select(1);
  var eventExpected = multiple ? "select" : "!select";
  synthesizeKeyExpectEvent(
    "VK_DOWN",
    { shiftKey: true },
    tree,
    eventExpected,
    "key shift down to select"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key shift down to select",
    multiple ? 2 : 1,
    multiple ? [1, 2] : [1]
  );
  is(
    selection.shiftSelectPivot,
    multiple ? 1 : -1,
    testid + "key shift down to select shiftSelectPivot"
  );
  synthesizeKeyExpectEvent(
    "VK_UP",
    { shiftKey: true },
    tree,
    eventExpected,
    "key shift up to unselect"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key shift up to unselect",
    1,
    [1]
  );
  is(
    selection.shiftSelectPivot,
    multiple ? 1 : -1,
    testid + "key shift up to unselect shiftSelectPivot"
  );
  if (multiple) {
    synthesizeKeyExpectEvent(
      "VK_UP",
      { shiftKey: true },
      tree,
      "select",
      "key shift up to select"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key shift up to select",
      0,
      [0, 1]
    );
    is(
      selection.shiftSelectPivot,
      1,
      testid + "key shift up to select shiftSelectPivot"
    );
    synthesizeKeyExpectEvent(
      "VK_DOWN",
      { shiftKey: true },
      tree,
      "select",
      "key shift down to unselect"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key shift down to unselect",
      1,
      [1]
    );
    is(
      selection.shiftSelectPivot,
      1,
      testid + "key shift down to unselect shiftSelectPivot"
    );
  }

  // do this twice, one for each state of pageUpOrDownMovesSelection, however
  // when selecting with the shift key, pageUpOrDownMovesSelection is ignored
  // and the selection always changes
  var lastidx = tree.view.rowCount - 1;
  for (let t = 0; t < 2; t++) {
    let testidmod = t == 0 ? "" : " rev";

    // If the top or bottom visible row is the current row, pressing shift and
    // page down / page up selects one page up or one page down. Otherwise, the
    // selection is made to the top or bottom of the visible area.
    tree.scrollToRow(lastidx - 3);
    selection.currentIndex = 6;
    selection.select(6);
    synthesizeKeyExpectEvent(
      "VK_PAGE_UP",
      { shiftKey: true },
      tree,
      eventExpected,
      "key shift page up"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key shift page up" + testidmod,
      multiple ? 4 : 6,
      multiple ? [4, 5, 6] : [6]
    );
    if (multiple) {
      synthesizeKeyExpectEvent(
        "VK_PAGE_UP",
        { shiftKey: true },
        tree,
        "select",
        "key shift page up again"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page up again" + testidmod,
        0,
        [0, 1, 2, 3, 4, 5, 6]
      );
      // no change in the selection, so no select event should be fired
      synthesizeKeyExpectEvent(
        "VK_PAGE_UP",
        { shiftKey: true },
        tree,
        "!select",
        "key shift page up at start"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page up at start" + testidmod,
        0,
        [0, 1, 2, 3, 4, 5, 6]
      );
      // deselect by paging down again
      synthesizeKeyExpectEvent(
        "VK_PAGE_DOWN",
        { shiftKey: true },
        tree,
        "select",
        "key shift page down deselect"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page down deselect" + testidmod,
        3,
        [3, 4, 5, 6]
      );
    }

    tree.scrollToRow(1);
    selection.currentIndex = 2;
    selection.select(2);
    synthesizeKeyExpectEvent(
      "VK_PAGE_DOWN",
      { shiftKey: true },
      tree,
      eventExpected,
      "key shift page down"
    );
    testtag_tree_TreeSelection_State(
      tree,
      testid + "key shift page down" + testidmod,
      multiple ? 4 : 2,
      multiple ? [2, 3, 4] : [2]
    );
    if (multiple) {
      synthesizeKeyExpectEvent(
        "VK_PAGE_DOWN",
        { shiftKey: true },
        tree,
        "select",
        "key shift page down again"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page down again" + testidmod,
        7,
        [2, 3, 4, 5, 6, 7]
      );
      synthesizeKeyExpectEvent(
        "VK_PAGE_DOWN",
        { shiftKey: true },
        tree,
        "!select",
        "key shift page down at start"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page down at start" + testidmod,
        7,
        [2, 3, 4, 5, 6, 7]
      );
      synthesizeKeyExpectEvent(
        "VK_PAGE_UP",
        { shiftKey: true },
        tree,
        "select",
        "key shift page up deselect"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page up deselect" + testidmod,
        4,
        [2, 3, 4]
      );
    }

    // test when page down / page up is pressed when the view is scrolled such
    // that the selection is not visible
    if (multiple) {
      tree.scrollToRow(3);
      selection.currentIndex = 1;
      selection.select(1);
      synthesizeKeyExpectEvent(
        "VK_PAGE_DOWN",
        { shiftKey: true },
        tree,
        eventExpected,
        "key shift page down with view scrolled down"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page down with view scrolled down" + testidmod,
        6,
        [1, 2, 3, 4, 5, 6],
        3
      );

      tree.scrollToRow(2);
      selection.currentIndex = 6;
      selection.select(6);
      synthesizeKeyExpectEvent(
        "VK_PAGE_UP",
        { shiftKey: true },
        tree,
        eventExpected,
        "key shift page up with view scrolled up"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page up with view scrolled up" + testidmod,
        2,
        [2, 3, 4, 5, 6],
        2
      );

      tree.scrollToRow(2);
      selection.currentIndex = 0;
      selection.select(0);
      // don't expect the select event, as the selection won't have changed
      synthesizeKeyExpectEvent(
        "VK_PAGE_UP",
        { shiftKey: true },
        tree,
        "!select",
        "key shift page up at start with view scrolled down"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid +
          "key shift page up at start with view scrolled down" +
          testidmod,
        0,
        [0],
        0
      );

      tree.scrollToRow(0);
      selection.currentIndex = 7;
      selection.select(7);
      // don't expect the select event, as the selection won't have changed
      synthesizeKeyExpectEvent(
        "VK_PAGE_DOWN",
        { shiftKey: true },
        tree,
        "!select",
        "key shift page down at end with view scrolled up"
      );
      testtag_tree_TreeSelection_State(
        tree,
        testid + "key shift page down at end with view scrolled up" + testidmod,
        7,
        [7],
        4
      );
    }

    tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection;
  }

  tree.scrollToRow(4);
  selection.select(5);
  synthesizeKeyExpectEvent(
    "VK_HOME",
    { shiftKey: true },
    tree,
    eventExpected,
    "key shift home"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key shift home",
    multiple ? 0 : 5,
    multiple ? [0, 1, 2, 3, 4, 5] : [5],
    multiple ? 0 : 4
  );

  tree.scrollToRow(0);
  selection.select(3);
  synthesizeKeyExpectEvent(
    "VK_END",
    { shiftKey: true },
    tree,
    eventExpected,
    "key shift end"
  );
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key shift end",
    multiple ? 7 : 3,
    multiple ? [3, 4, 5, 6, 7] : [3],
    multiple ? 4 : 0
  );

  // pressing space selects a row, pressing accel + space unselects a row
  selection.select(2);
  selection.currentIndex = 4;
  synthesizeKeyExpectEvent(" ", {}, tree, "select""key space on");
  // in single selection mode, space shouldn't do anything
  testtag_tree_TreeSelection_State(
    tree,
    testid + "key space on",
    4,
    multiple ? [2, 4] : [2]
  );

  if (multiple) {
    synthesizeKeyExpectEvent(
      " ",
      { accelKey: true },
      tree,
      "select",
      "key space off"
    );
    testtag_tree_TreeSelection_State(tree, testid + "key space off", 4, [2]);
  }

  // check that clicking on a row selects it
  tree.scrollToRow(0);
  selection.select(2);
  selection.currentIndex = 2;
  if (0) {
    // XXXndeakin disable these tests for now
    mouseOnCell(tree, 1, tree.columns[1], "mouse on row");
    testtag_tree_TreeSelection_State(
      tree,
      testid + "mouse on row",
      1,
      [1],
      0,
      null
    );
  }

  // restore the scroll position to the start of the page
  sendKey("HOME");

  window.removeEventListener("keydown", keydownListener);
  window.removeEventListener("keypress", keypressListener);
  is(keydownFired, multiple ? 63 : 40, "keydown event wasn't fired properly");
  is(keypressFired, multiple ? 2 : 1, "keypress event wasn't fired properly");
}

function testtag_tree_UI_editing(tree, testid, rowInfo) {
  testid += " editing UI ";

  // check editing UI
  var ecolumn = tree.columns[0];
  var rowIndex = 2;

  // temporary make the tree editable to test mouse double click
  var wasEditable = tree.editable;
  if (!wasEditable) {
    tree.editable = true;
  }

  // if this is a container save its current open status
  var row = rowInfo.rows[rowIndex];
  var wasOpen = null;
  if (tree.view.isContainer(row)) {
    wasOpen = tree.view.isContainerOpen(row);
  }

  mouseDblClickOnCell(tree, rowIndex, ecolumn, testid + "edit on double click");
  is(tree.editingColumn, ecolumn, testid + "editing column");
  is(tree.editingRow, rowIndex, testid + "editing row");

  // ensure that we don't expand an expandable container on edit
  if (wasOpen != null) {
    is(
      tree.view.isContainerOpen(row),
      wasOpen,
      testid + "opened container node on edit"
    );
  }

  // ensure to restore editable attribute
  if (!wasEditable) {
    tree.editable = false;
  }

  var ci = tree.currentIndex;

  // cursor navigation should not change the selection while editing
  var testKey = function (key) {
    synthesizeKeyExpectEvent(
      key,
      {},
      tree,
      "!select",
      "key " + key + " with editing"
    );
    is(
      tree.editingRow == rowIndex &&
        tree.editingColumn == ecolumn &&
        tree.currentIndex == ci,
      true,
      testid + "key " + key + " while editing"
    );
  };

  testKey("VK_DOWN");
  testKey("VK_UP");
  testKey("VK_PAGE_DOWN");
  testKey("VK_PAGE_UP");
  testKey("VK_HOME");
  testKey("VK_END");

  // XXXndeakin figure out how to send characters to the textbox
  // inputField.inputField.focus()
  // synthesizeKeyExpectEvent(inputField.inputField, "b", null, "");
  // tree.stopEditing(true);
  // is(tree.view.getCellText(0, ecolumn), "b", testid + "edit cell");

  // Restore initial state.
  tree.stopEditing(false);
}

function testtag_tree_TreeView(tree, testid, rowInfo) {
  testid += " view ";

  var columns = tree.columns;
  var view = tree.view;

  is(view instanceof Ci.nsITreeView, true, testid + "view is a TreeView");
  is(view.rowCount, rowInfo.rows.length, testid + "rowCount");

  testtag_tree_TreeView_rows(tree, testid, rowInfo, 0);

  // note that this will only work for content trees currently
  view.setCellText(0, columns[1], "Changed Value");
  is(view.getCellText(0, columns[1]), "Changed Value""setCellText");

  view.setCellValue(1, columns[0], "Another Changed Value");
  is(view.getCellValue(1, columns[0]), "Another Changed Value""setCellText");
}

function testtag_tree_TreeView_rows(tree, testid, rowInfo, startRow) {
  var r;
  var columns = tree.columns;
  var view = tree.view;
  var length = rowInfo.rows.length;

  // methods to test along with the functions which determine the expected value
  var checkRowMethods = {
    isContainer(row) {
      return row.container;
    },
    isContainerOpen() {
      return false;
    },
    isContainerEmpty(row) {
      return row.children != null && !row.children.rows.length;
    },
    isSeparator(row) {
      return row.separator;
    },
    getRowProperties(row) {
      return row.properties;
    },
    getLevel(row) {
      return row.level;
    },
    getParentIndex(row) {
      return row.parent;
    },
    hasNextSibling() {
      return r < startRow + length - 1;
    },
  };

  var checkCellMethods = {
    getCellText(row, cell) {
      return cell.label;
    },
    getCellValue(row, cell) {
      return cell.value;
    },
    getCellProperties(row, cell) {
      return cell.properties;
    },
    isEditable(row, cell) {
      return cell.editable;
    },
    getImageSrc(row, cell) {
      return cell.image;
    },
  };

  var failedMethods = {};
  var checkMethod, actual, expected;
  var toggleOpenStateOK = true;

  for (r = startRow; r < length; r++) {
    var row = rowInfo.rows[r];
    for (var c = 0; c < row.cells.length; c++) {
      var cell = row.cells[c];

      for (checkMethod in checkCellMethods) {
        expected = checkCellMethods[checkMethod](row, cell);
        actual = view[checkMethod](r, columns[c]);
        if (actual !== expected) {
          failedMethods[checkMethod] = true;
          is(
            actual,
            expected,
            testid +
              "row " +
              r +
              " column " +
              c +
              " " +
              checkMethod +
              " is incorrect"
          );
        }
      }
    }

    // compare row properties
    for (checkMethod in checkRowMethods) {
      expected = checkRowMethods[checkMethod](row, r);
      if (checkMethod == "hasNextSibling") {
        actual = view[checkMethod](r, r);
      } else {
        actual = view[checkMethod](r);
      }
      if (actual !== expected) {
        failedMethods[checkMethod] = true;
        is(
          actual,
          expected,
          testid + "row " + r + " " + checkMethod + " is incorrect"
        );
      }
    }
    /*
    // open and recurse into containers
    if (row.container) {
      view.toggleOpenState(r);
      if (!view.isContainerOpen(r)) {
        toggleOpenStateOK = false;
        is(view.isContainerOpen(r), true, testid + "row " + r + " toggleOpenState open");
      }
      testtag_tree_TreeView_rows(tree, testid + "container " + r + " ", row.children, r + 1);
      view.toggleOpenState(r);
      if (view.isContainerOpen(r)) {
        toggleOpenStateOK = false;
        is(view.isContainerOpen(r), false, testid + "row " + r + " toggleOpenState close");
      }
    }
*/

  }

  for (var failedMethod in failedMethods) {
    if (failedMethod in checkRowMethods) {
      delete checkRowMethods[failedMethod];
    }
    if (failedMethod in checkCellMethods) {
      delete checkCellMethods[failedMethod];
    }
  }

  for (checkMethod in checkRowMethods) {
    is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod);
  }
  for (checkMethod in checkCellMethods) {
    is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod);
  }
  if (toggleOpenStateOK) {
    is("toggleOpenState ok""toggleOpenState ok", testid + "toggleOpenState");
  }
}

function testtag_tree_TreeView_rows_sort(tree) {
  // check if cycleHeader sorts the columns
  var columnIndex = 0;
  var view = tree.view;
  var column = tree.columns[columnIndex];
  var columnElement = column.element;
  var sortkey = columnElement.getAttribute("sort");
  if (sortkey) {
    view.cycleHeader(column);
    is(tree.getAttribute("sort"), sortkey, "cycleHeader sort");
    is(
      tree.getAttribute("sortDirection"),
      "ascending",
      "cycleHeader sortDirection ascending"
    );
    is(
      columnElement.getAttribute("sortDirection"),
      "ascending",
      "cycleHeader column sortDirection"
    );
    is(
      columnElement.getAttribute("sortActive"),
      "true",
      "cycleHeader column sortActive"
    );
    view.cycleHeader(column);
    is(
      tree.getAttribute("sortDirection"),
      "descending",
      "cycleHeader sortDirection descending"
    );
    is(
      columnElement.getAttribute("sortDirection"),
      "descending",
      "cycleHeader column sortDirection descending"
    );
    view.cycleHeader(column);
    is(
      tree.getAttribute("sortDirection"),
      "",
      "cycleHeader sortDirection natural"
    );
    is(
      columnElement.getAttribute("sortDirection"),
      "",
      "cycleHeader column sortDirection natural"
    );
    // XXXndeakin content view isSorted needs to be tested
  }

  // Check that clicking on column header sorts the column.
  var columns = getSortedColumnArray(tree);
  is(
    columnElement.getAttribute("sortDirection"),
    "",
    "cycleHeader column sortDirection"
  );

  // Click once on column header and check sorting has cycled once.
  mouseClickOnColumnHeader(columns, columnIndex, 0, 1);
  is(
    columnElement.getAttribute("sortDirection"),
    "ascending",
    "single click cycleHeader column sortDirection ascending"
  );

  // Now simulate a double click.
  mouseClickOnColumnHeader(columns, columnIndex, 0, 2);
  if (navigator.platform.indexOf("Win") == 0) {
    // Windows cycles only once on double click.
    is(
      columnElement.getAttribute("sortDirection"),
      "descending",
      "double click cycleHeader column sortDirection descending"
    );
    // 1 single clicks should restore natural sorting.
    mouseClickOnColumnHeader(columns, columnIndex, 0, 1);
  }

  // Check we have gone back to natural sorting.
  is(
    columnElement.getAttribute("sortDirection"),
    "",
    "cycleHeader column sortDirection"
  );

  columnElement.setAttribute("sorthints""twostate");
  view.cycleHeader(column);
  is(
    tree.getAttribute("sortDirection"),
    "ascending",
    "cycleHeader sortDirection ascending twostate"
  );
  view.cycleHeader(column);
  is(
    tree.getAttribute("sortDirection"),
    "descending",
    "cycleHeader sortDirection ascending twostate"
  );
  view.cycleHeader(column);
  is(
    tree.getAttribute("sortDirection"),
    "ascending",
    "cycleHeader sortDirection ascending twostate again"
  );
  columnElement.removeAttribute("sorthints");
  view.cycleHeader(column);
  view.cycleHeader(column);

  is(
    columnElement.getAttribute("sortDirection"),
    "",
    "cycleHeader column sortDirection reset"
  );
}

// checks if the current and selected rows are correct
// current is the index of the current row
// selected is an array of the indicies of the selected rows
// viewidx is the row that should be visible at the top of the tree
function testtag_tree_TreeSelection_State(
  tree,
  testid,
  current,
  selected,
  viewidx
) {
  var selection = tree.view.selection;

  is(selection.count, selected.length, testid + " count");
  is(tree.currentIndex, current, testid + " currentIndex");
  is(selection.currentIndex, current, testid + " TreeSelection currentIndex");
  if (viewidx !== null && viewidx !== undefined) {
    is(tree.getFirstVisibleRow(), viewidx, testid + " first visible row");
  }

  var actualSelected = [];
  var count = tree.view.rowCount;
  for (var s = 0; s < count; s++) {
    if (selection.isSelected(s)) {
      actualSelected.push(s);
    }
  }

  is(
    compareArrays(selected, actualSelected),
    true,
    testid + " selection [" + selected + "]"
  );

  actualSelected = [];
  var rangecount = selection.getRangeCount();
  for (var r = 0; r < rangecount; r++) {
    var start = {},
      end = {};
    selection.getRangeAt(r, start, end);
    for (var rs = start.value; rs <= end.value; rs++) {
      actualSelected.push(rs);
    }
  }

  is(
    compareArrays(selected, actualSelected),
    true,
    testid + " range selection [" + selected + "]"
  );
}

function testtag_tree_column_reorder() {
  // Make sure the tree is scrolled into the view, otherwise the test will
  // fail
  var testframe = window.parent.document.getElementById("testframe");
  if (testframe) {
    testframe.scrollIntoView();
  }

  var tree = document.getElementById("tree-column-reorder");
  var numColumns = tree.columns.count;

  var reference = [];
  for (let i = 0; i < numColumns; i++) {
    reference.push("col_" + i);
  }

  // Drag the first column to each position
  for (let i = 0; i < numColumns - 1; i++) {
    synthesizeColumnDrag(tree, i, i + 1, true);
    arrayMove(reference, i, i + 1, true);
    checkColumns(tree, reference, "drag first column right");
  }

  // And back
  for (let i = numColumns - 1; i >= 1; i--) {
    synthesizeColumnDrag(tree, i, i - 1, false);
    arrayMove(reference, i, i - 1, false);
    checkColumns(tree, reference, "drag last column left");
  }

  // Drag each column one column left
  for (let i = 1; i < numColumns; i++) {
    synthesizeColumnDrag(tree, i, i - 1, false);
    arrayMove(reference, i, i - 1, false);
    checkColumns(tree, reference, "drag each column left");
  }

  // And back
  for (let i = numColumns - 2; i >= 0; i--) {
    synthesizeColumnDrag(tree, i, i + 1, true);
    arrayMove(reference, i, i + 1, true);
    checkColumns(tree, reference, "drag each column right");
  }

  // Drag each column 5 to the right
  for (let i = 0; i < numColumns - 5; i++) {
    synthesizeColumnDrag(tree, i, i + 5, true);
    arrayMove(reference, i, i + 5, true);
    checkColumns(tree, reference, "drag each column 5 to the right");
  }

  // And to the left
  for (let i = numColumns - 6; i >= 5; i--) {
    synthesizeColumnDrag(tree, i, i - 5, false);
    arrayMove(reference, i, i - 5, false);
    checkColumns(tree, reference, "drag each column 5 to the left");
  }

  // Test that moving a column after itself does not move anything
  synthesizeColumnDrag(tree, 0, 0, true);
  checkColumns(tree, reference, "drag to itself");
  is(document.treecolDragging, null"drag to itself completed");

  // XXX roc should this be here???
  SimpleTest.finish();
}

function testtag_tree_wheel(aTree) {
  const deltaModes = [
    WheelEvent.DOM_DELTA_PIXEL, // 0
    WheelEvent.DOM_DELTA_LINE, // 1
    WheelEvent.DOM_DELTA_PAGE, // 2
  ];
  function helper(aStart, aDelta, aIntDelta, aDeltaMode) {
    aTree.scrollToRow(aStart);
    var expected;
    if (!aIntDelta) {
      expected = aStart;
    } else if (aDeltaMode != WheelEvent.DOM_DELTA_PAGE) {
      expected = aStart + aIntDelta;
    } else if (aIntDelta > 0) {
      expected = aStart + aTree.getPageLength();
    } else {
      expected = aStart - aTree.getPageLength();
    }

    if (expected < 0) {
      expected = 0;
    }
    if (expected > aTree.view.rowCount - aTree.getPageLength()) {
      expected = aTree.view.rowCount - aTree.getPageLength();
    }
    synthesizeWheel(aTree.body, 1, 1, {
      deltaMode: aDeltaMode,
      deltaY: aDelta,
      lineOrPageDeltaY: aIntDelta,
    });
    is(
      aTree.getFirstVisibleRow(),
      expected,
      "testtag_tree_wheel: vertical, starting " +
        aStart +
        " delta " +
        aDelta +
        " lineOrPageDelta " +
        aIntDelta +
        " aDeltaMode " +
        aDeltaMode
    );

    aTree.scrollToRow(aStart);
    // Check that horizontal scrolling has no effect
    synthesizeWheel(aTree.body, 1, 1, {
      deltaMode: aDeltaMode,
      deltaX: aDelta,
      lineOrPageDeltaX: aIntDelta,
    });
    is(
      aTree.getFirstVisibleRow(),
      aStart,
      "testtag_tree_wheel: horizontal, starting " +
        aStart +
        " delta " +
        aDelta +
        " lineOrPageDelta " +
        aIntDelta +
        " aDeltaMode " +
        aDeltaMode
    );
  }

  var defaultPrevented = 0;

  function wheelListener() {
    defaultPrevented++;
  }
  window.addEventListener("wheel", wheelListener);

  deltaModes.forEach(function (aDeltaMode) {
    var delta = aDeltaMode == WheelEvent.DOM_DELTA_PIXEL ? 5.0 : 0.3;
    helper(2, -delta, 0, aDeltaMode);
    helper(2, -delta, -1, aDeltaMode);
    helper(2, delta, 0, aDeltaMode);
    helper(2, delta, 1, aDeltaMode);
    helper(2, -2 * delta, 0, aDeltaMode);
    helper(2, -2 * delta, -1, aDeltaMode);
    helper(2, 2 * delta, 0, aDeltaMode);
    helper(2, 2 * delta, 1, aDeltaMode);
  });

  window.removeEventListener("wheel", wheelListener);
  is(defaultPrevented, 48, "wheel event default prevented");
}

async function testtag_tree_scroll() {
  const tree = document.querySelector("tree");

  info("Scroll down with the content scrollbar at the top");
  await doScrollTest({
    tree,
    initialTreeScrollRow: 0,
    initialContainerScrollTop: 0,
    scrollDelta: 10,
    isTreeScrollExpected: true,
  });

  info("Scroll down with the content scrollbar at the middle");
  await doScrollTest({
    tree,
    initialTreeScrollRow: 3,
    initialContainerScrollTop: 0,
    scrollDelta: 10,
    isTreeScrollExpected: true,
  });

  info("Scroll down with the content scrollbar at the bottom");
  await doScrollTest({
    tree,
    initialTreeScrollRow: 9,
    initialContainerScrollTop: 0,
    scrollDelta: 10,
    isTreeScrollExpected: false,
  });

  info("Scroll up with the content scrollbar at the bottom");
  await doScrollTest({
    tree,
    initialTreeScrollRow: 9,
    initialContainerScrollTop: 50,
    scrollDelta: -10,
    isTreeScrollExpected: true,
  });

  info("Scroll up with the content scrollbar at the middle");
  await doScrollTest({
    tree,
    initialTreeScrollRow: 5,
    initialContainerScrollTop: 50,
    scrollDelta: -10,
    isTreeScrollExpected: true,
  });

  info("Scroll up with the content scrollbar at the top");
  await doScrollTest({
    tree,
    initialTreeScrollRow: 0,
    initialContainerScrollTop: 50,
    scrollDelta: -10,
    isTreeScrollExpected: false,
  });

  info("Check whether the tree is not scrolled when the parent is scrolling");
  await doScrollWhileScrollingParent(tree);

  info(
    "Check whether the tree component consumes wheel events even if the scroll is located at edge as long as the events are handled as the same series"
  );
  await doScrollInSameSeries({
    tree,
    initialTreeScrollRow: 0,
    initialContainerScrollTop: 0,
    scrollDelta: 10,
  });
  await doScrollInSameSeries({
    tree,
    initialTreeScrollRow: 9,
    initialContainerScrollTop: 50,
    scrollDelta: -10,
  });

  SimpleTest.finish();
}

async function doScrollInSameSeries({
  tree,
  initialTreeScrollRow,
  initialContainerScrollTop,
  scrollDelta,
}) {
  // Set enough value to mousewheel.scroll_series_timeout pref to ensure the wheel
  // event fired as the same series.
  Services.prefs.setIntPref("mousewheel.scroll_series_timeout", 1000);

  const scrollbar = tree.shadowRoot.querySelector(
    "scrollbar[orient='vertical']"
  );
  const parent = tree.parentElement;

  tree.scrollToRow(initialTreeScrollRow);
  parent.scrollTop = initialContainerScrollTop;

  // Scroll until the scrollbar was moved to the specified amount.
  await SimpleTest.promiseWaitForCondition(async () => {
    await nativeScroll(tree, 10, 10, scrollDelta);
    const curpos = scrollbar.getAttribute("curpos");
    return (
      (scrollDelta < 0 && curpos == 0) ||
      (scrollDelta > 0 && curpos == scrollbar.getAttribute("maxpos"))
    );
  });

  // More scroll as the same series.
  for (let i = 0; i < 10; i++) {
    await nativeScroll(tree, 10, 10, scrollDelta);
  }

  is(
    parent.scrollTop,
    initialContainerScrollTop,
    "The wheel events are condumed in tree component"
  );
  const utils = SpecialPowers.getDOMWindowUtils(window);
  ok(!utils.getWheelScrollTarget(), "The parent should not handle the event");

  Services.prefs.clearUserPref("mousewheel.scroll_series_timeout");
}

async function doScrollWhileScrollingParent(tree) {
  // Set enough value to mousewheel.scroll_series_timeout pref to ensure the wheel
  // event fired as the same series.
  Services.prefs.setIntPref("mousewheel.scroll_series_timeout", 1000);

  const scrollbar = tree.shadowRoot.querySelector(
    "scrollbar[orient='vertical']"
  );
  const parent = tree.parentElement;

  // Set initial scroll amount.
  tree.scrollToRow(0);
  parent.scrollTop = 0;

  const scrollAmount = scrollbar.getAttribute("curpos");

  // Scroll parent from top to bottom.
  await SimpleTest.promiseWaitForCondition(async () => {
    await nativeScroll(parent, 10, 10, 10);
    return parent.scrollTop === parent.scrollTopMax;
  });

  is(
    scrollAmount,
    scrollbar.getAttribute("curpos"),
    "The tree should not be scrolled"
  );

  const utils = SpecialPowers.getDOMWindowUtils(window);
  await SimpleTest.promiseWaitForCondition(() => !utils.getWheelScrollTarget());
  Services.prefs.clearUserPref("mousewheel.scroll_series_timeout");
}

async function doScrollTest({
  tree,
  initialTreeScrollRow,
  initialContainerScrollTop,
  scrollDelta,
  isTreeScrollExpected,
}) {
  const scrollbar = tree.shadowRoot.querySelector(
    "scrollbar[orient='vertical']"
  );
  const container = tree.parentElement;

  // Set initial scroll amount.
  tree.scrollToRow(initialTreeScrollRow);
  container.scrollTop = initialContainerScrollTop;

  const treeScrollAmount = scrollbar.getAttribute("curpos");
  const containerScrollAmount = container.scrollTop;

  // Wait until changing either scroll.
  await SimpleTest.promiseWaitForCondition(async () => {
    await nativeScroll(tree, 10, 10, scrollDelta);
    return (
      treeScrollAmount !== scrollbar.getAttribute("curpos") ||
      containerScrollAmount !== container.scrollTop
    );
  });

  is(
    treeScrollAmount !== scrollbar.getAttribute("curpos"),
    isTreeScrollExpected,
    "Scroll of tree is expected"
  );
  is(
    containerScrollAmount !== container.scrollTop,
    !isTreeScrollExpected,
    "Scroll of container is expected"
  );

  // Wait until finishing wheel scroll transaction.
  const utils = SpecialPowers.getDOMWindowUtils(window);
  await SimpleTest.promiseWaitForCondition(() => !utils.getWheelScrollTarget());
}

async function nativeScroll(component, offsetX, offsetY, scrollDelta) {
  const utils = SpecialPowers.getDOMWindowUtils(window);
  const x = component.screenX + offsetX;
  const y = component.screenY + offsetY;

  // Mouse move event.
  await new Promise(resolve => {
    info("waiting for mousemove");
    window.addEventListener("mousemove", resolve, { once: true });
    utils.sendNativeMouseEvent(
      x * window.devicePixelRatio,
      y * window.devicePixelRatio,
      utils.NATIVE_MOUSE_MESSAGE_MOVE,
      0,
      {},
      component
    );
  });

  // Wheel event.
  await new Promise(resolve => {
    info("waiting for wheel");
    window.addEventListener("wheel", resolve, { once: true });
    utils.sendNativeMouseScrollEvent(
      x * window.devicePixelRatio,
      y * window.devicePixelRatio,
      // nativeVerticalWheelEventMsg is defined in apz_test_native_event_utils.js
      // eslint-disable-next-line no-undef
      nativeVerticalWheelEventMsg(),
      0,
      // nativeScrollUnits is defined in apz_test_native_event_utils.js
      // eslint-disable-next-line no-undef
      -nativeScrollUnits(component, scrollDelta),
      0,
      0,
      0,
      component
    );
  });

  info("waiting for apz");
  // promiseApzFlushedRepaints is defined in apz_test_utils.js
  // eslint-disable-next-line no-undef
  await promiseApzFlushedRepaints();
}

function synthesizeColumnDrag(
  aTree,
  aMouseDownColumnNumber,
  aMouseUpColumnNumber,
  aAfter
) {
  var columns = getSortedColumnArray(aTree);

  var down = columns[aMouseDownColumnNumber].element;
  var up = columns[aMouseUpColumnNumber].element;

  // Target the initial mousedown in the middle of the column header so we
  // avoid the extra hit test space given to the splitter
  var columnWidth = down.getBoundingClientRect().width;
  var splitterHitWidth = columnWidth / 2;
  synthesizeMouse(down, splitterHitWidth, 3, { type: "mousedown" });

  var offsetX = 0;
  if (aAfter) {
    offsetX = columnWidth;
  }

  if (aMouseUpColumnNumber > aMouseDownColumnNumber) {
    for (let i = aMouseDownColumnNumber; i <= aMouseUpColumnNumber; i++) {
      let move = columns[i].element;
      synthesizeMouse(move, offsetX, 3, { type: "mousemove" });
    }
  } else {
    for (let i = aMouseDownColumnNumber; i >= aMouseUpColumnNumber; i--) {
      let move = columns[i].element;
      synthesizeMouse(move, offsetX, 3, { type: "mousemove" });
    }
  }

  synthesizeMouse(up, offsetX, 3, { type: "mouseup" });
}

function arrayMove(aArray, aFrom, aTo, aAfter) {
  var o = aArray.splice(aFrom, 1)[0];
  if (aTo > aFrom) {
    aTo--;
  }

  if (aAfter) {
    aTo++;
  }

  aArray.splice(aTo, 0, o);
}

function getSortedColumnArray(aTree) {
  var columns = aTree.columns;
  var array = [];
  for (let i = 0; i < columns.length; i++) {
    array.push(columns.getColumnAt(i));
  }

  array.sort(function (a, b) {
    var o1 = parseInt(a.element.style.order);
    var o2 = parseInt(b.element.style.order);
    return o1 - o2;
  });
  return array;
}

function checkColumns(aTree, aReference, aMessage) {
  var columns = getSortedColumnArray(aTree);
  var ids = [];
  columns.forEach(function (e) {
    ids.push(e.element.id);
  });
  is(compareArrays(ids, aReference), true, aMessage);
}

function mouseOnCell(tree, row, column, testname) {
  var rect = tree.getCoordsForCellItem(row, column, "text");

  synthesizeMouseExpectEvent(
    tree.body,
    rect.x,
    rect.y,
    {},
    tree,
    "select",
    testname
  );
}

function mouseClickOnColumnHeader(
  aColumns,
  aColumnIndex,
  aButton,
  aClickCount
) {
  var columnHeader = aColumns[aColumnIndex].element;
  var columnHeaderRect = columnHeader.getBoundingClientRect();
  var columnWidth = columnHeaderRect.right - columnHeaderRect.left;
  // For multiple click we send separate click events, with increasing
  // clickCount.  This simulates the common behavior of multiple clicks.
  for (let i = 1; i <= aClickCount; i++) {
    // Target the middle of the column header.
    synthesizeMouse(columnHeader, columnWidth / 2, 3, {
      button: aButton,
      clickCount: i,
    });
  }
}

function mouseDblClickOnCell(tree, row, column) {
  // select the row we will edit
  var selection = tree.view.selection;
  selection.select(row);
  tree.ensureRowIsVisible(row);

  // get cell coordinates
  var rect = tree.getCoordsForCellItem(row, column, "text");

  synthesizeMouse(tree.body, rect.x, rect.y, { clickCount: 2 });
}

function compareArrays(arr1, arr2) {
  if (arr1.length != arr2.length) {
    return false;
  }

  for (let i = 0; i < arr1.length; i++) {
    if (arr1[i] != arr2[i]) {
      return false;
    }
  }

  return true;
}

function convertDOMtoTreeRowInfo(treechildren, level, rowidx) {
  var obj = { rows: [] };

  var parentidx = rowidx.value;

  treechildren = treechildren.childNodes;
  for (var r = 0; r < treechildren.length; r++) {
    rowidx.value++;

    var treeitem = treechildren[r];
    if (treeitem.hasChildNodes()) {
      var treerow = treeitem.firstChild;
      var cellInfo = [];
      for (var c = 0; c < treerow.childNodes.length; c++) {
        var cell = treerow.childNodes[c];
        cellInfo.push({
          label: cell.getAttribute("label") || "",
          value: cell.getAttribute("value") || "",
          properties: cell.getAttribute("properties") || "",
          editable: cell.getAttribute("editable") != "false",
          selectable: cell.getAttribute("selectable") != "false",
          image: cell.getAttribute("src") || "",
          mode: cell.hasAttribute("mode")
            ? parseInt(cell.getAttribute("mode"))
            : 3,
        });
      }

      var descendants = treeitem.lastChild;
      var children =
        treerow == descendants
          ? null
          : convertDOMtoTreeRowInfo(descendants, level + 1, rowidx);
      obj.rows.push({
        cells: cellInfo,
        properties: treerow.getAttribute("properties") || "",
        container: treeitem.getAttribute("container") == "true",
        separator: treeitem.localName == "treeseparator",
        children,
        level,
        parent: parentidx,
      });
    }
  }

  return obj;
}

Messung V0.5
C=95 H=91 G=92

¤ Dauer der Verarbeitung: 0.28 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge