diff options
Diffstat (limited to 'toolkit/content/tests/widgets/tree_shared.js')
-rw-r--r-- | toolkit/content/tests/widgets/tree_shared.js | 1405 |
1 files changed, 1405 insertions, 0 deletions
diff --git a/toolkit/content/tests/widgets/tree_shared.js b/toolkit/content/tests/widgets/tree_shared.js new file mode 100644 index 000000000..b157bdf56 --- /dev/null +++ b/toolkit/content/tests/widgets/tree_shared.js @@ -0,0 +1,1405 @@ +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, progressmeter 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. +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, false); + + 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.view.selection.currentColumn, null, testid + " initial currentColumn"); + is(tree.selType, seltype == "multiple" ? "" : seltype, testid + " seltype"); + + // note: the functions below should be in this order due to changes made in later tests + + // select the first column in cell selection mode so that the selection + // functions can be tested + if (seltype == "cell") + tree.view.selection.currentColumn = tree.columns[0]; + + testtag_tree_columns(tree, columnInfo, testid); + testtag_tree_TreeSelection(tree, testid, multiple); + testtag_tree_TreeSelection_UI(tree, testid, multiple); + if (seltype == "cell") + testtag_tree_TreeSelection_UI_cell(tree, testid, rowInfo); + + 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 instanceof Components.interfaces.nsIDOMXULTextBoxElement, true, 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, false); + + SimpleTest.finish(); +} + +function testtag_tree_columns(tree, expectedColumns, testid) +{ + testid += " "; + + var columns = tree.columns; + + is(columns instanceof TreeColumns, 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(column instanceof TreeColumn, 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.selectable, true, adjtestid + "column is selectable"); + 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 ? 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 Components.interfaces.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, []); + + // XXXndeakin invertSelection isn't implemented + // selection.invertSelection(); + + 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(event) + { + keydownFired++; + } + function keypressListener(event) { + 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, false); + window.addEventListener("keypress", keypressListener, false); + + 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.treeBoxObject.scrollToRow(4); + selection.select(4); + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up with scroll"); + is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with scroll"); + + tree.treeBoxObject.scrollToRow(0); + selection.select(3); + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down with scroll"); + is(tree.treeBoxObject.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.treeBoxObject.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.treeBoxObject.getFirstVisibleRow(), 1, testid + "key down with accel and scroll"); + + tree.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.scrollToRow(4); + selection.select(6); + synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); + testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0); + + tree.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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.treeBoxObject.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, + tree.selType == "cell" ? tree.columns[1] : null); + } + + // restore the scroll position to the start of the page + sendKey("HOME"); + + window.removeEventListener("keydown", keydownListener, false); + window.removeEventListener("keypress", keypressListener, false); + 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; + var inputField = tree.inputField; + + // 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); + + // Test whether a keystroke can enter text entry, and another can exit. + if (tree.selType == "cell") + { + tree.stopEditing(false); + ok(!tree.editingColumn, "Should not be editing tree cell now"); + tree.view.selection.currentColumn = ecolumn; + tree.currentIndex = rowIndex; + + const isMac = (navigator.platform.indexOf("Mac") >= 0); + const StartEditingKey = isMac ? "RETURN" : "F2"; + sendKey(StartEditingKey); + is(tree.editingColumn, ecolumn, "Should be editing tree cell now"); + sendKey("ESCAPE"); + ok(!tree.editingColumn, "Should not be editing tree cell now"); + is(tree.currentIndex, rowIndex, "Current index should not have changed"); + is(tree.view.selection.currentColumn, ecolumn, "Current column should not have changed"); + } + + 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_TreeSelection_UI_cell(tree, testid, rowInfo) +{ + testid += " selection UI cell "; + + var columns = tree.columns; + var firstcolumn = columns[0]; + var secondcolumn = columns[1]; + var lastcolumn = columns[columns.length - 1]; + var secondlastcolumn = columns[columns.length - 2]; + var selection = tree.view.selection; + + selection.clearSelection(); + selection.currentIndex = -1; + selection.currentColumn = firstcolumn; + is(selection.currentColumn, firstcolumn, testid + " first currentColumn"); + + // no selection yet so nothing should happen when the left and right cursor keys are pressed + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right no selection"); + testtag_tree_TreeSelection_State(tree, testid + "key right no selection", -1, [], null, firstcolumn); + + selection.currentColumn = secondcolumn; + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left no selection"); + testtag_tree_TreeSelection_State(tree, testid + "key left no selection", -1, [], null, secondcolumn); + + selection.select(2); + selection.currentIndex = 2; + if (0) { // XXXndeakin disable these tests for now + mouseOnCell(tree, 1, secondlastcolumn, "mouse on cell"); + testtag_tree_TreeSelection_State(tree, testid + "mouse on cell", 1, [1], null, secondlastcolumn); + } + + tree.focus(); + + // selection is set, so it should move when the left and right cursor keys are pressed + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + selection.currentIndex = 1; + selection.currentColumn = secondcolumn; + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in second column"); + testtag_tree_TreeSelection_State(tree, testid + "key left in second column", 1, [1], 0, firstcolumn); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in first column"); + testtag_tree_TreeSelection_State(tree, testid + "key left in first column", 1, [1], 0, firstcolumn); + + selection.currentColumn = secondlastcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in second last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right in second last column", 1, [1], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right in last column", 1, [1], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in second row"); + testtag_tree_TreeSelection_State(tree, testid + "key up in second row", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in first row"); + testtag_tree_TreeSelection_State(tree, testid + "key up in first row", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in first row"); + testtag_tree_TreeSelection_State(tree, testid + "key down in first row", 1, [1], 0, lastcolumn); + + var lastidx = tree.view.rowCount - 1; + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.select(lastidx); + selection.currentIndex = lastidx; + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in last row"); + testtag_tree_TreeSelection_State(tree, testid + "key down in last row", lastidx, [lastidx], lastidx - 3, lastcolumn); + + synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); + testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_END", {}, tree, "!select", "key end"); + testtag_tree_TreeSelection_State(tree, testid + "key end", lastidx, [lastidx], lastidx - 3, lastcolumn); + + for (var t = 0; t < 2; t++) { + var testidmod = (t == 0) ? "" : " rev"; + + // scroll to the end, subtract 3 because we want lastidx to appear + // at the end of view + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.select(lastidx); + selection.currentIndex = lastidx; + var expectedrow = tree.pageUpOrDownMovesSelection ? lastidx - 3 : lastidx; + synthesizeKeyExpectEvent("VK_PAGE_UP", {}, tree, "!select", "key page up"); + testtag_tree_TreeSelection_State(tree, testid + "key page up" + testidmod, + expectedrow, [expectedrow], + tree.pageUpOrDownMovesSelection ? lastidx - 3 : 0, lastcolumn); + + tree.treeBoxObject.scrollToRow(1); + selection.select(1); + selection.currentIndex = 1; + expectedrow = tree.pageUpOrDownMovesSelection ? 4 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", {}, tree, "!select", "key page down"); + testtag_tree_TreeSelection_State(tree, testid + "key page down" + testidmod, + expectedrow, [expectedrow], + tree.pageUpOrDownMovesSelection ? 1 : lastidx - 3, lastcolumn); + + tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; + } + + // now check navigation when there is unselctable column + secondcolumn.element.setAttribute("selectable", "false"); + secondcolumn.invalidate(); + is(secondcolumn.selectable, false, testid + "set selectable attribute"); + + if (columns.length >= 3) { + selection.select(3); + selection.currentIndex = 3; + // check whether unselectable columns are skipped over + selection.currentColumn = firstcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable column"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable column", + 3, [3], null, secondcolumn.getNext()); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable column"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable column", + 3, [3], null, firstcolumn); + } + + secondcolumn.element.removeAttribute("selectable"); + secondcolumn.invalidate(); + is(secondcolumn.selectable, true, testid + "clear selectable attribute"); + + // check to ensure that navigation isn't allowed if the first column is not selectable + selection.currentColumn = secondcolumn; + firstcolumn.element.setAttribute("selectable", "false"); + firstcolumn.invalidate(); + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable first column"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable first column", + 3, [3], null, secondcolumn); + firstcolumn.element.removeAttribute("selectable"); + firstcolumn.invalidate(); + + // check to ensure that navigation isn't allowed if the last column is not selectable + selection.currentColumn = secondlastcolumn; + lastcolumn.element.setAttribute("selectable", "false"); + lastcolumn.invalidate(); + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable last column", + 3, [3], null, secondlastcolumn); + lastcolumn.element.removeAttribute("selectable"); + lastcolumn.invalidate(); + + // now check for cells with selectable false + if (!rowInfo.rows[4].cells[1].selectable && columns.length >= 3) { + // check whether unselectable cells are skipped over + selection.select(4); + selection.currentIndex = 4; + + selection.currentColumn = firstcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable cell", + 4, [4], null, secondcolumn.getNext()); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable cell", + 4, [4], null, firstcolumn); + + tree.treeBoxObject.scrollToRow(1); + selection.select(3); + selection.currentIndex = 3; + selection.currentColumn = secondcolumn; + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key down unselectable cell", + 5, [5], 2, secondcolumn); + + tree.treeBoxObject.scrollToRow(4); + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key up unselectable cell", + 3, [3], 3, secondcolumn); + } + + // restore the scroll position to the start of the page + sendKey("HOME"); +} + +function testtag_tree_TreeView(tree, testid, rowInfo) +{ + testid += " view "; + + var columns = tree.columns; + var view = tree.view; + + is(view instanceof Components.interfaces.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: function(row) { return row.container }, + isContainerOpen: function(row) { return false }, + isContainerEmpty: function(row) { return (row.children != null && row.children.rows.length == 0) }, + isSeparator: function(row) { return row.separator }, + getRowProperties: function(row) { return row.properties }, + getLevel: function(row) { return row.level }, + getParentIndex: function(row) { return row.parent }, + hasNextSibling: function(row) { return r < startRow + length - 1; } + }; + + var checkCellMethods = + { + getCellText: function(row, cell) { return cell.label }, + getCellValue: function(row, cell) { return cell.value }, + getCellProperties: function(row, cell) { return cell.properties }, + isEditable: function(row, cell) { return cell.editable }, + isSelectable: function(row, cell) { return cell.selectable }, + getImageSrc: function(row, cell) { return cell.image }, + getProgressMode: function(row, cell) { return cell.mode } + }; + + var failedMethods = { }; + var checkMethod, actual, expected; + var containerInfo = null; + 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, testid, rowInfo) +{ + // 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 +// column is the selected column +// viewidx is the row that should be visible at the top of the tree +function testtag_tree_TreeSelection_State(tree, testid, current, selected, viewidx, column) +{ + var selection = tree.view.selection; + + if (!column) + column = (tree.selType == "cell") ? tree.columns[0] : null; + + is(selection.count, selected.length, testid + " count"); + is(tree.currentIndex, current, testid + " currentIndex"); + is(selection.currentIndex, current, testid + " TreeSelection currentIndex"); + is(selection.currentColumn, column, testid + " currentColumn"); + if (viewidx !== null && viewidx !== undefined) + is(tree.treeBoxObject.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.treeBoxObject.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.treeBoxObject.getPageLength(); + } + else { + expected = aStart - aTree.treeBoxObject.getPageLength(); + } + + if (expected < 0) { + expected = 0; + } + if (expected > aTree.view.rowCount - aTree.treeBoxObject.getPageLength()) { + expected = aTree.view.rowCount - aTree.treeBoxObject.getPageLength(); + } + synthesizeWheel(aTree.body, 1, 1, + { deltaMode: aDeltaMode, deltaY: aDelta, + lineOrPageDeltaY: aIntDelta }); + is(aTree.treeBoxObject.getFirstVisibleRow(), expected, + "testtag_tree_wheel: vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + + aTree.treeBoxObject.scrollToRow(aStart); + // Check that horizontal scrolling has no effect + synthesizeWheel(aTree.body, 1, 1, + { deltaMode: aDeltaMode, deltaX: aDelta, + lineOrPageDeltaX: aIntDelta }); + is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, + "testtag_tree_wheel: horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + } + + var defaultPrevented = 0; + + function wheelListener(event) { + defaultPrevented++; + } + window.addEventListener("wheel", wheelListener, false); + + 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, false); + is(defaultPrevented, 48, "wheel event default prevented"); +} + +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.boxObject.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.getAttribute("ordinal")); + var o2 = parseInt(b.element.getAttribute("ordinal")); + 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.boxObject.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 }, null); + } +} + +function mouseDblClickOnCell(tree, row, column, testname) +{ + // select the row we will edit + var selection = tree.view.selection; + selection.select(row); + tree.treeBoxObject.ensureRowIsVisible(row); + + // get cell coordinates + var rect = tree.treeBoxObject.getCoordsForCellItem(row, column, "text"); + + synthesizeMouse(tree.body, rect.x, rect.y, { clickCount: 2 }, null); +} + +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 convertProperties(arr) +{ + var results = []; + var count = arr.Count(); + for (let i = 0; i < count; i++) + results.push(arr.GetElementAt(i).QueryInterface(Components.interfaces.nsIAtom).toString()); + + results.sort(); + return results.join(" "); +} + +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: children, + level: level, + parent: parentidx }); + } + } + + return obj; +} |