// 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.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"
);
// 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);
}
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 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"
);
// 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"
);
// 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"
);
// 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, []);
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_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";
} elseif (t == 1) {
testidmod = " rev";
} var keymod = t == 2 ? { accelKey: true } : {};
var moveselection = tree.pageUpOrDownMovesSelection; if (t == 2) {
moveselection = !moveselection;
}
// 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]
);
}
// 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 = 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
);
}
// 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");
// 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);
}
// 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"
);
};
// 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");
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() { returnfalse;
},
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 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"
);
// 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;
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();
}
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);
// 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");
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);
is(
treeScrollAmount !== scrollbar.getAttribute("curpos"),
isTreeScrollExpected, "Scroll of tree is expected"
);
is(
containerScrollAmount !== container.scrollTop,
!isTreeScrollExpected, "Scroll of container is expected"
);
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" });
}
}
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");
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");
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.