michael@0: var columns_simpletree = michael@0: [ michael@0: { name: "name", label: "Name", key: true, properties: "one two" }, michael@0: { name: "address", label: "Address" } michael@0: ]; michael@0: michael@0: var columns_hiertree = michael@0: [ michael@0: { name: "name", label: "Name", primary: true, key: true, properties: "one two" }, michael@0: { name: "address", label: "Address" }, michael@0: { name: "planet", label: "Planet" }, michael@0: { name: "gender", label: "Gender", cycler: true } michael@0: ]; michael@0: michael@0: // XXXndeakin still to add some tests for: michael@0: // cycler columns, checkbox cells, progressmeter cells michael@0: michael@0: // this test function expects a tree to have 8 rows in it when it isn't michael@0: // expanded. The tree should only display four rows at a time. If editable, michael@0: // the cell at row 1 and column 0 must be editable, and the cell at row 2 and michael@0: // column 1 must not be editable. michael@0: function testtag_tree(treeid, treerowinfoid, seltype, columnstype, testid) michael@0: { michael@0: // Stop keystrokes that aren't handled by the tree from leaking out and michael@0: // scrolling the main Mochitests window! michael@0: function preventDefault(event) { michael@0: event.preventDefault(); michael@0: } michael@0: document.addEventListener("keypress", preventDefault, false); michael@0: michael@0: var multiple = (seltype == "multiple"); michael@0: michael@0: var tree = document.getElementById(treeid); michael@0: var treerowinfo = document.getElementById(treerowinfoid); michael@0: var rowInfo; michael@0: if (testid =="tree view") michael@0: rowInfo = getCustomTreeViewCellInfo(); michael@0: else michael@0: rowInfo = convertDOMtoTreeRowInfo(treerowinfo, 0, { value: -1 }); michael@0: var columnInfo = (columnstype == "simple") ? columns_simpletree : columns_hiertree; michael@0: michael@0: is(tree.view.selection.currentColumn, null, testid + " initial currentColumn"); michael@0: is(tree.selType, seltype == "multiple" ? "" : seltype, testid + " seltype"); michael@0: michael@0: // note: the functions below should be in this order due to changes made in later tests michael@0: michael@0: // select the first column in cell selection mode so that the selection michael@0: // functions can be tested michael@0: if (seltype == "cell") michael@0: tree.view.selection.currentColumn = tree.columns[0]; michael@0: michael@0: testtag_tree_columns(tree, columnInfo, testid); michael@0: testtag_tree_TreeSelection(tree, testid, multiple); michael@0: testtag_tree_TreeSelection_UI(tree, testid, multiple); michael@0: if (seltype == "cell") michael@0: testtag_tree_TreeSelection_UI_cell(tree, testid, rowInfo); michael@0: michael@0: testtag_tree_TreeView(tree, testid, rowInfo); michael@0: michael@0: is(tree.editable, false, "tree should not be editable"); michael@0: // currently, the editable flag means that tree editing cannot be invoked michael@0: // by the user. However, editing can still be started with a script. michael@0: is(tree.editingRow, -1, testid + " initial editingRow"); michael@0: is(tree.editingColumn, null, testid + " initial editingColumn"); michael@0: michael@0: testtag_tree_UI_editing(tree, testid, rowInfo); michael@0: michael@0: is(tree.editable, false, "tree should not be editable after testtag_tree_UI_editing"); michael@0: // currently, the editable flag means that tree editing cannot be invoked michael@0: // by the user. However, editing can still be started with a script. michael@0: is(tree.editingRow, -1, testid + " initial editingRow (continued)"); michael@0: is(tree.editingColumn, null, testid + " initial editingColumn (continued)"); michael@0: michael@0: var ecolumn = tree.columns[0]; michael@0: ok(!tree.startEditing(1, ecolumn), "non-editable trees shouldn't start editing"); michael@0: is(tree.editingRow, -1, testid + " failed startEditing shouldn't set editingRow"); michael@0: is(tree.editingColumn, null, testid + " failed startEditing shouldn't set editingColumn"); michael@0: michael@0: tree.editable = true; michael@0: michael@0: ok(tree.startEditing(1, ecolumn), "startEditing should have returned true"); michael@0: is(tree.editingRow, 1, testid + " startEditing editingRow"); michael@0: is(tree.editingColumn, ecolumn, testid + " startEditing editingColumn"); michael@0: is(tree.getAttribute("editing"), "true", testid + " startEditing editing attribute"); michael@0: michael@0: tree.stopEditing(true); michael@0: is(tree.editingRow, -1, testid + " stopEditing editingRow"); michael@0: is(tree.editingColumn, null, testid + " stopEditing editingColumn"); michael@0: is(tree.hasAttribute("editing"), false, testid + " stopEditing editing attribute"); michael@0: michael@0: tree.startEditing(-1, ecolumn); michael@0: is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing -1 editingRow"); michael@0: tree.startEditing(15, ecolumn); michael@0: is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing 15 editingRow"); michael@0: tree.startEditing(1, null); michael@0: is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing null column editingRow"); michael@0: tree.startEditing(2, tree.columns[1]); michael@0: is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing non editable cell editingRow"); michael@0: michael@0: tree.startEditing(1, ecolumn); michael@0: var inputField = tree.inputField; michael@0: is(inputField instanceof Components.interfaces.nsIDOMXULTextBoxElement, true, testid + "inputField"); michael@0: inputField.value = "Changed Value"; michael@0: tree.stopEditing(true); michael@0: is(tree.view.getCellText(1, ecolumn), "Changed Value", testid + "edit cell accept"); michael@0: michael@0: // this cell can be edited, but stopEditing(false) means don't accept the change. michael@0: tree.startEditing(1, ecolumn); michael@0: inputField.value = "Second Value"; michael@0: tree.stopEditing(false); michael@0: is(tree.view.getCellText(1, ecolumn), "Changed Value", testid + "edit cell no accept"); michael@0: michael@0: tree.editable = false; michael@0: michael@0: // do the sorting tests last as it will cause the rows to rearrange michael@0: // skip them for the custom tree view michael@0: if (testid !="tree view") michael@0: testtag_tree_TreeView_rows_sort(tree, testid, rowInfo); michael@0: michael@0: testtag_tree_wheel(tree); michael@0: michael@0: document.removeEventListener("keypress", preventDefault, false); michael@0: michael@0: SimpleTest.finish(); michael@0: } michael@0: michael@0: function testtag_tree_columns(tree, expectedColumns, testid) michael@0: { michael@0: testid += " "; michael@0: michael@0: var columns = tree.columns; michael@0: michael@0: is(columns instanceof TreeColumns, true, testid + "columns is a TreeColumns"); michael@0: is(columns.count, expectedColumns.length, testid + "TreeColumns count"); michael@0: is(columns.length, expectedColumns.length, testid + "TreeColumns length"); michael@0: michael@0: var treecols = tree.getElementsByTagName("treecols")[0]; michael@0: var treecol = treecols.getElementsByTagName("treecol"); michael@0: michael@0: var x = 0; michael@0: var primary = null, sorted = null, key = null; michael@0: for (var c = 0; c < expectedColumns.length; c++) { michael@0: var adjtestid = testid + " column " + c + " "; michael@0: var column = columns[c]; michael@0: var expectedColumn = expectedColumns[c]; michael@0: is(columns.getColumnAt(c), column, adjtestid + "getColumnAt"); michael@0: is(columns.getNamedColumn(expectedColumn.name), column, adjtestid + "getNamedColumn"); michael@0: is(columns.getColumnFor(treecol[c]), column, adjtestid + "getColumnFor"); michael@0: if (expectedColumn.primary) michael@0: primary = column; michael@0: if (expectedColumn.sorted) michael@0: sorted = column; michael@0: if (expectedColumn.key) michael@0: key = column; michael@0: michael@0: // XXXndeakin on Windows and Linux, some columns are one pixel to the michael@0: // left of where they should be. Could just be a rounding issue. michael@0: var adj = 1; michael@0: is(column.x + adj >= x, true, adjtestid + "position is after last column " + michael@0: column.x + "," + column.width + "," + x); michael@0: is(column.width > 0, true, adjtestid + "width is greater than 0"); michael@0: x = column.x + column.width; michael@0: michael@0: // now check the TreeColumn properties michael@0: is(column instanceof TreeColumn, true, adjtestid + "is a TreeColumn"); michael@0: is(column.element, treecol[c], adjtestid + "element is treecol"); michael@0: is(column.columns, columns, adjtestid + "columns is TreeColumns"); michael@0: is(column.id, expectedColumn.name, adjtestid + "name"); michael@0: is(column.index, c, adjtestid + "index"); michael@0: is(column.primary, primary == column, adjtestid + "column is primary"); michael@0: michael@0: is(column.cycler, "cycler" in expectedColumn && expectedColumn.cycler, michael@0: adjtestid + "column is cycler"); michael@0: is(column.selectable, true, adjtestid + "column is selectable"); michael@0: is(column.editable, "editable" in expectedColumn && expectedColumn.editable, michael@0: adjtestid + "column is editable"); michael@0: michael@0: is(column.type, "type" in expectedColumn ? expectedColumn.type : 1, adjtestid + "type"); michael@0: michael@0: is(column.getPrevious(), c > 0 ? columns[c - 1] : null, adjtestid + "getPrevious"); michael@0: is(column.getNext(), c < columns.length - 1 ? columns[c + 1] : null, adjtestid + "getNext"); michael@0: michael@0: // check the view's getColumnProperties method michael@0: var properties = tree.view.getColumnProperties(column); michael@0: var expectedProperties = expectedColumn.properties; michael@0: is(properties, expectedProperties ? expectedProperties : "", adjtestid + "getColumnProperties"); michael@0: } michael@0: michael@0: is(columns.getFirstColumn(), columns[0], testid + "getFirstColumn"); michael@0: is(columns.getLastColumn(), columns[columns.length - 1], testid + "getLastColumn"); michael@0: is(columns.getPrimaryColumn(), primary, testid + "getPrimaryColumn"); michael@0: is(columns.getSortedColumn(), sorted, testid + "getSortedColumn"); michael@0: is(columns.getKeyColumn(), key, testid + "getKeyColumn"); michael@0: michael@0: is(columns.getColumnAt(-1), null, testid + "getColumnAt under"); michael@0: is(columns.getColumnAt(columns.length), null, testid + "getColumnAt over"); michael@0: is(columns.getNamedColumn(""), null, testid + "getNamedColumn null"); michael@0: is(columns.getNamedColumn("unknown"), null, testid + "getNamedColumn unknown"); michael@0: is(columns.getColumnFor(null), null, testid + "getColumnFor null"); michael@0: is(columns.getColumnFor(tree), null, testid + "getColumnFor other"); michael@0: } michael@0: michael@0: function testtag_tree_TreeSelection(tree, testid, multiple) michael@0: { michael@0: testid += " selection "; michael@0: michael@0: var selection = tree.view.selection; michael@0: is(selection instanceof Components.interfaces.nsITreeSelection, true, michael@0: testid + "selection is a TreeSelection"); michael@0: is(selection.single, !multiple, testid + "single"); michael@0: michael@0: testtag_tree_TreeSelection_State(tree, testid + "initial", -1, []); michael@0: is(selection.shiftSelectPivot, -1, testid + "initial shiftSelectPivot"); michael@0: michael@0: selection.currentIndex = 2; michael@0: testtag_tree_TreeSelection_State(tree, testid + "set currentIndex", 2, []); michael@0: tree.currentIndex = 3; michael@0: testtag_tree_TreeSelection_State(tree, testid + "set tree.currentIndex", 3, []); michael@0: michael@0: // test the select() method, which should deselect all rows and select michael@0: // a single row michael@0: selection.select(1); michael@0: testtag_tree_TreeSelection_State(tree, testid + "select 1", 1, [1]); michael@0: selection.select(3); michael@0: testtag_tree_TreeSelection_State(tree, testid + "select 2", 3, [3]); michael@0: selection.select(3); michael@0: testtag_tree_TreeSelection_State(tree, testid + "select same", 3, [3]); michael@0: michael@0: selection.currentIndex = 1; michael@0: testtag_tree_TreeSelection_State(tree, testid + "set currentIndex with single selection", 1, [3]); michael@0: michael@0: tree.currentIndex = 2; michael@0: testtag_tree_TreeSelection_State(tree, testid + "set tree.currentIndex with single selection", 2, [3]); michael@0: michael@0: // check the toggleSelect method. In single selection mode, it only toggles on when michael@0: // there isn't currently a selection. michael@0: selection.toggleSelect(2); michael@0: testtag_tree_TreeSelection_State(tree, testid + "toggleSelect 1", 2, multiple ? [2, 3] : [3]); michael@0: selection.toggleSelect(2); michael@0: selection.toggleSelect(3); michael@0: testtag_tree_TreeSelection_State(tree, testid + "toggleSelect 2", 3, []); michael@0: michael@0: // the current index doesn't change after a selectAll, so it should still be set to 1 michael@0: // selectAll has no effect on single selection trees michael@0: selection.currentIndex = 1; michael@0: selection.selectAll(); michael@0: testtag_tree_TreeSelection_State(tree, testid + "selectAll 1", 1, multiple ? [0, 1, 2, 3, 4, 5, 6 , 7] : []); michael@0: selection.toggleSelect(2); michael@0: testtag_tree_TreeSelection_State(tree, testid + "toggleSelect after selectAll", 2, michael@0: multiple ? [0, 1, 3, 4, 5, 6, 7] : [2]); michael@0: selection.clearSelection(); michael@0: testtag_tree_TreeSelection_State(tree, testid + "clearSelection", 2, []); michael@0: selection.toggleSelect(3); michael@0: selection.toggleSelect(1); michael@0: if (multiple) { michael@0: selection.selectAll(); michael@0: testtag_tree_TreeSelection_State(tree, testid + "selectAll 2", 1, [0, 1, 2, 3, 4, 5, 6, 7]); michael@0: } michael@0: selection.currentIndex = 2; michael@0: selection.clearSelection(); michael@0: testtag_tree_TreeSelection_State(tree, testid + "clearSelection after selectAll", 2, []); michael@0: michael@0: // XXXndeakin invertSelection isn't implemented michael@0: // selection.invertSelection(); michael@0: michael@0: is(selection.shiftSelectPivot, -1, testid + "shiftSelectPivot set to -1"); michael@0: michael@0: // rangedSelect and clearRange set the currentIndex to the endIndex. The michael@0: // shiftSelectPivot property will be set to startIndex. michael@0: selection.rangedSelect(1, 3, false); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect no augment", michael@0: multiple ? 3 : 2, multiple ? [1, 2, 3] : []); michael@0: is(selection.shiftSelectPivot, multiple ? 1 : -1, michael@0: testid + "shiftSelectPivot after rangedSelect no augment"); michael@0: if (multiple) { michael@0: selection.select(1); michael@0: selection.rangedSelect(0, 2, true); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment", 2, [0, 1, 2]); michael@0: is(selection.shiftSelectPivot, 0, testid + "shiftSelectPivot after rangedSelect augment"); michael@0: michael@0: selection.clearRange(1, 3); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment", 3, [0]); michael@0: michael@0: // check that rangedSelect can take a start value higher than end michael@0: selection.rangedSelect(3, 1, false); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect reverse", 1, [1, 2, 3]); michael@0: is(selection.shiftSelectPivot, 3, testid + "shiftSelectPivot after rangedSelect reverse"); michael@0: michael@0: // check that setting the current index doesn't change the selection michael@0: selection.currentIndex = 0; michael@0: testtag_tree_TreeSelection_State(tree, testid + "currentIndex with range selection", 0, [1, 2, 3]); michael@0: } michael@0: michael@0: // both values of rangedSelect may be the same michael@0: selection.rangedSelect(2, 2, false); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect one row", 2, [2]); michael@0: is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot after selecting one row"); michael@0: michael@0: if (multiple) { michael@0: selection.rangedSelect(2, 3, true); michael@0: michael@0: // a start index of -1 means from the last point michael@0: selection.rangedSelect(-1, 0, true); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect -1 existing selection", 0, [0, 1, 2, 3]); michael@0: is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot after -1 existing selection"); michael@0: michael@0: selection.currentIndex = 2; michael@0: selection.rangedSelect(-1, 0, false); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect -1 from currentIndex", 0, [0, 1, 2]); michael@0: is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot -1 from currentIndex"); michael@0: } michael@0: michael@0: // XXXndeakin need to test out of range values but these don't work properly michael@0: /* michael@0: selection.select(-1); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment -1", -1, []); michael@0: michael@0: selection.select(8); michael@0: testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment 8", 3, [0]); michael@0: */ michael@0: } michael@0: michael@0: function testtag_tree_TreeSelection_UI(tree, testid, multiple) michael@0: { michael@0: testid += " selection UI "; michael@0: michael@0: var selection = tree.view.selection; michael@0: selection.clearSelection(); michael@0: selection.currentIndex = 0; michael@0: tree.focus(); michael@0: michael@0: var keydownFired = 0; michael@0: var keypressFired = 0; michael@0: function keydownListener(event) michael@0: { michael@0: keydownFired++; michael@0: } michael@0: function keypressListener(event) { michael@0: keypressFired++; michael@0: } michael@0: michael@0: // check that cursor up and down keys navigate up and down michael@0: // select event fires after a delay so don't expect it. The reason it fires after a delay michael@0: // is so that cursor navigation allows quicking skimming over a set of items without michael@0: // actually firing events in-between, improving performance. The select event will only michael@0: // be fired on the row where the cursor stops. michael@0: window.addEventListener("keydown", keydownListener, false); michael@0: window.addEventListener("keypress", keypressListener, false); michael@0: michael@0: synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key down", 1, [1], 0); michael@0: michael@0: synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key up", 0, [0], 0); michael@0: michael@0: synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up at start"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key up at start", 0, [0], 0); michael@0: michael@0: // pressing down while the last row is selected should not fire a select event, michael@0: // as the selection won't have changed. Also the view is not scrolled in this case. michael@0: selection.select(7); michael@0: synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down at end"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key down at end", 7, [7], 0); michael@0: michael@0: // pressing keys while at the edge of the visible rows should scroll the list michael@0: tree.treeBoxObject.scrollToRow(4); michael@0: selection.select(4); michael@0: synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up with scroll"); michael@0: is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with scroll"); michael@0: michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.select(3); michael@0: synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down with scroll"); michael@0: is(tree.treeBoxObject.getFirstVisibleRow(), 1, testid + "key down with scroll"); michael@0: michael@0: // accel key and cursor movement adjust currentIndex but should not change michael@0: // the selection. In single selection mode, the selection will not change, michael@0: // but instead will just scroll up or down a line michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.select(1); michael@0: synthesizeKeyExpectEvent("VK_DOWN", { accelKey: true }, tree, "!select", "key down with accel"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key down with accel", multiple ? 2 : 1, [1]); michael@0: if (!multiple) michael@0: is(tree.treeBoxObject.getFirstVisibleRow(), 1, testid + "key down with accel and scroll"); michael@0: michael@0: tree.treeBoxObject.scrollToRow(4); michael@0: selection.select(4); michael@0: synthesizeKeyExpectEvent("VK_UP", { accelKey: true }, tree, "!select", "key up with accel"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key up with accel", multiple ? 3 : 4, [4]); michael@0: if (!multiple) michael@0: is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with accel and scroll"); michael@0: michael@0: // do this three times, one for each state of pageUpOrDownMovesSelection, michael@0: // and then once with the accel key pressed michael@0: for (var t = 0; t < 3; t++) { michael@0: var testidmod = (t == 2) ? " with accel" : (t == 1) ? " rev" : ""; michael@0: var keymod = (t == 2) ? { accelKey: true } : { }; michael@0: michael@0: var moveselection = tree.pageUpOrDownMovesSelection; michael@0: if (t == 2) michael@0: moveselection = !moveselection; michael@0: michael@0: tree.treeBoxObject.scrollToRow(4); michael@0: selection.currentIndex = 6; michael@0: selection.select(6); michael@0: var expected = moveselection ? 4 : 6; michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page up" + testidmod, michael@0: expected, [expected], moveselection ? 4 : 0); michael@0: michael@0: expected = moveselection ? 0 : 6; michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up again"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page up again" + testidmod, michael@0: expected, [expected], 0); michael@0: michael@0: expected = moveselection ? 0 : 6; michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up at start"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page up at start" + testidmod, michael@0: expected, [expected], 0); michael@0: michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.currentIndex = 1; michael@0: selection.select(1); michael@0: expected = moveselection ? 3 : 1; michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page down" + testidmod, michael@0: expected, [expected], moveselection ? 0 : 4); michael@0: michael@0: expected = moveselection ? 7 : 1; michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down again"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page down again" + testidmod, michael@0: expected, [expected], 4); michael@0: michael@0: expected = moveselection ? 7 : 1; michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down at start"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page down at start" + testidmod, michael@0: expected, [expected], 4); michael@0: michael@0: if (t < 2) michael@0: tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; michael@0: } michael@0: michael@0: tree.treeBoxObject.scrollToRow(4); michael@0: selection.select(6); michael@0: synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0); michael@0: michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.select(1); michael@0: synthesizeKeyExpectEvent("VK_END", {}, tree, "!select", "key end"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key end", 7, [7], 4); michael@0: michael@0: // in single selection mode, the selection doesn't change in this case michael@0: tree.treeBoxObject.scrollToRow(4); michael@0: selection.select(6); michael@0: synthesizeKeyExpectEvent("VK_HOME", { accelKey: true }, tree, "!select", "key home with accel"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key home with accel", multiple ? 0 : 6, [6], 0); michael@0: michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.select(1); michael@0: synthesizeKeyExpectEvent("VK_END", { accelKey: true }, tree, "!select", "key end with accel"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key end with accel", multiple ? 7 : 1, [1], 4); michael@0: michael@0: // next, test cursor navigation with selection. Here the select event will be fired michael@0: selection.select(1); michael@0: var eventExpected = multiple ? "select" : "!select"; michael@0: synthesizeKeyExpectEvent("VK_DOWN", { shiftKey: true }, tree, eventExpected, "key shift down to select"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift down to select", michael@0: multiple ? 2 : 1, multiple ? [1, 2] : [1]); michael@0: is(selection.shiftSelectPivot, multiple ? 1 : -1, michael@0: testid + "key shift down to select shiftSelectPivot"); michael@0: synthesizeKeyExpectEvent("VK_UP", { shiftKey: true }, tree, eventExpected, "key shift up to unselect"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift up to unselect", 1, [1]); michael@0: is(selection.shiftSelectPivot, multiple ? 1 : -1, michael@0: testid + "key shift up to unselect shiftSelectPivot"); michael@0: if (multiple) { michael@0: synthesizeKeyExpectEvent("VK_UP", { shiftKey: true }, tree, "select", "key shift up to select"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift up to select", 0, [0, 1]); michael@0: is(selection.shiftSelectPivot, 1, testid + "key shift up to select shiftSelectPivot"); michael@0: synthesizeKeyExpectEvent("VK_DOWN", { shiftKey: true }, tree, "select", "key shift down to unselect"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift down to unselect", 1, [1]); michael@0: is(selection.shiftSelectPivot, 1, testid + "key shift down to unselect shiftSelectPivot"); michael@0: } michael@0: michael@0: // do this twice, one for each state of pageUpOrDownMovesSelection, however michael@0: // when selecting with the shift key, pageUpOrDownMovesSelection is ignored michael@0: // and the selection always changes michael@0: var lastidx = tree.view.rowCount - 1; michael@0: for (var t = 0; t < 2; t++) { michael@0: var testidmod = (t == 0) ? "" : " rev"; michael@0: michael@0: // If the top or bottom visible row is the current row, pressing shift and michael@0: // page down / page up selects one page up or one page down. Otherwise, the michael@0: // selection is made to the top or bottom of the visible area. michael@0: tree.treeBoxObject.scrollToRow(lastidx - 3); michael@0: selection.currentIndex = 6; michael@0: selection.select(6); michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, eventExpected, "key shift page up"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page up" + testidmod, michael@0: multiple ? 4 : 6, multiple ? [4, 5, 6] : [6]); michael@0: if (multiple) { michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "select", "key shift page up again"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page up again" + testidmod, michael@0: 0, [0, 1, 2, 3, 4, 5, 6]); michael@0: // no change in the selection, so no select event should be fired michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "!select", "key shift page up at start"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page up at start" + testidmod, michael@0: 0, [0, 1, 2, 3, 4, 5, 6]); michael@0: // deselect by paging down again michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "select", "key shift page down deselect"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page down deselect" + testidmod, michael@0: 3, [3, 4, 5, 6]); michael@0: } michael@0: michael@0: tree.treeBoxObject.scrollToRow(1); michael@0: selection.currentIndex = 2; michael@0: selection.select(2); michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, eventExpected, "key shift page down"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page down" + testidmod, michael@0: multiple ? 4 : 2, multiple ? [2, 3, 4] : [2]); michael@0: if (multiple) { michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "select", "key shift page down again"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page down again" + testidmod, michael@0: 7, [2, 3, 4, 5, 6, 7]); michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "!select", "key shift page down at start"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page down at start" + testidmod, michael@0: 7, [2, 3, 4, 5, 6, 7]); michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "select", "key shift page up deselect"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page up deselect" + testidmod, michael@0: 4, [2, 3, 4]); michael@0: } michael@0: michael@0: // test when page down / page up is pressed when the view is scrolled such michael@0: // that the selection is not visible michael@0: if (multiple) { michael@0: tree.treeBoxObject.scrollToRow(3); michael@0: selection.currentIndex = 1; michael@0: selection.select(1); michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, eventExpected, michael@0: "key shift page down with view scrolled down"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page down with view scrolled down" + testidmod, michael@0: 6, [1, 2, 3, 4, 5, 6], 3); michael@0: michael@0: tree.treeBoxObject.scrollToRow(2); michael@0: selection.currentIndex = 6; michael@0: selection.select(6); michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, eventExpected, michael@0: "key shift page up with view scrolled up"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page up with view scrolled up" + testidmod, michael@0: 2, [2, 3, 4, 5, 6], 2); michael@0: michael@0: tree.treeBoxObject.scrollToRow(2); michael@0: selection.currentIndex = 0; michael@0: selection.select(0); michael@0: // don't expect the select event, as the selection won't have changed michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "!select", michael@0: "key shift page up at start with view scrolled down"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page up at start with view scrolled down" + testidmod, michael@0: 0, [0], 0); michael@0: michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.currentIndex = 7; michael@0: selection.select(7); michael@0: // don't expect the select event, as the selection won't have changed michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "!select", michael@0: "key shift page down at end with view scrolled up"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift page down at end with view scrolled up" + testidmod, michael@0: 7, [7], 4); michael@0: } michael@0: michael@0: tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; michael@0: } michael@0: michael@0: tree.treeBoxObject.scrollToRow(4); michael@0: selection.select(5); michael@0: synthesizeKeyExpectEvent("VK_HOME", { shiftKey: true }, tree, eventExpected, "key shift home"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift home", michael@0: multiple ? 0 : 5, multiple ? [0, 1, 2, 3, 4, 5] : [5], multiple ? 0 : 4); michael@0: michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.select(3); michael@0: synthesizeKeyExpectEvent("VK_END", { shiftKey: true }, tree, eventExpected, "key shift end"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key shift end", michael@0: multiple ? 7 : 3, multiple ? [3, 4, 5, 6, 7] : [3], multiple ? 4 : 0); michael@0: michael@0: // pressing space selects a row, pressing accel + space unselects a row michael@0: selection.select(2); michael@0: selection.currentIndex = 4; michael@0: synthesizeKeyExpectEvent(" ", {}, tree, "select", "key space on"); michael@0: // in single selection mode, space shouldn't do anything michael@0: testtag_tree_TreeSelection_State(tree, testid + "key space on", 4, multiple ? [2, 4] : [2]); michael@0: michael@0: if (multiple) { michael@0: synthesizeKeyExpectEvent(" ", { accelKey: true }, tree, "select", "key space off"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key space off", 4, [2]); michael@0: } michael@0: michael@0: // check that clicking on a row selects it michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.select(2); michael@0: selection.currentIndex = 2; michael@0: if (0) { // XXXndeakin disable these tests for now michael@0: mouseOnCell(tree, 1, tree.columns[1], "mouse on row"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "mouse on row", 1, [1], 0, michael@0: tree.selType == "cell" ? tree.columns[1] : null); michael@0: } michael@0: michael@0: // restore the scroll position to the start of the page michael@0: sendKey("HOME"); michael@0: michael@0: window.removeEventListener("keydown", keydownListener, false); michael@0: window.removeEventListener("keypress", keypressListener, false); michael@0: is(keydownFired, multiple ? 63 : 40, "keydown event wasn't fired properly"); michael@0: is(keypressFired, multiple ? 2 : 1, "keypress event wasn't fired properly"); michael@0: } michael@0: michael@0: function testtag_tree_UI_editing(tree, testid, rowInfo) michael@0: { michael@0: testid += " editing UI "; michael@0: michael@0: // check editing UI michael@0: var ecolumn = tree.columns[0]; michael@0: var rowIndex = 2; michael@0: var inputField = tree.inputField; michael@0: michael@0: // temporary make the tree editable to test mouse double click michael@0: var wasEditable = tree.editable; michael@0: if (!wasEditable) michael@0: tree.editable = true; michael@0: michael@0: // if this is a container save its current open status michael@0: var row = rowInfo.rows[rowIndex]; michael@0: var wasOpen = null; michael@0: if (tree.view.isContainer(row)) michael@0: wasOpen = tree.view.isContainerOpen(row); michael@0: michael@0: // Test whether a keystroke can enter text entry, and another can exit. michael@0: if (tree.selType == "cell") michael@0: { michael@0: tree.stopEditing(false); michael@0: ok(!tree.editingColumn, "Should not be editing tree cell now"); michael@0: tree.view.selection.currentColumn = ecolumn; michael@0: tree.currentIndex = rowIndex; michael@0: michael@0: const isMac = (navigator.platform.indexOf("Mac") >= 0); michael@0: const StartEditingKey = isMac ? "RETURN" : "F2"; michael@0: sendKey(StartEditingKey); michael@0: is(tree.editingColumn, ecolumn, "Should be editing tree cell now"); michael@0: sendKey("ESCAPE"); michael@0: ok(!tree.editingColumn, "Should not be editing tree cell now"); michael@0: is(tree.currentIndex, rowIndex, "Current index should not have changed"); michael@0: is(tree.view.selection.currentColumn, ecolumn, "Current column should not have changed"); michael@0: } michael@0: michael@0: mouseDblClickOnCell(tree, rowIndex, ecolumn, testid + "edit on double click"); michael@0: is(tree.editingColumn, ecolumn, testid + "editing column"); michael@0: is(tree.editingRow, rowIndex, testid + "editing row"); michael@0: michael@0: // ensure that we don't expand an expandable container on edit michael@0: if (wasOpen != null) michael@0: is(tree.view.isContainerOpen(row), wasOpen, testid + "opened container node on edit"); michael@0: michael@0: // ensure to restore editable attribute michael@0: if (!wasEditable) michael@0: tree.editable = false; michael@0: michael@0: var ci = tree.currentIndex; michael@0: michael@0: // cursor navigation should not change the selection while editing michael@0: var testKey = function(key) { michael@0: synthesizeKeyExpectEvent(key, {}, tree, "!select", "key " + key + " with editing"); michael@0: is(tree.editingRow == rowIndex && tree.editingColumn == ecolumn && tree.currentIndex == ci, michael@0: true, testid + "key " + key + " while editing"); michael@0: } michael@0: michael@0: testKey("VK_DOWN"); michael@0: testKey("VK_UP"); michael@0: testKey("VK_PAGE_DOWN"); michael@0: testKey("VK_PAGE_UP"); michael@0: testKey("VK_HOME"); michael@0: testKey("VK_END"); michael@0: michael@0: // XXXndeakin figure out how to send characters to the textbox michael@0: // inputField.inputField.focus() michael@0: // synthesizeKeyExpectEvent(inputField.inputField, "b", null, ""); michael@0: // tree.stopEditing(true); michael@0: // is(tree.view.getCellText(0, ecolumn), "b", testid + "edit cell"); michael@0: michael@0: // Restore initial state. michael@0: tree.stopEditing(false); michael@0: } michael@0: michael@0: function testtag_tree_TreeSelection_UI_cell(tree, testid, rowInfo) michael@0: { michael@0: testid += " selection UI cell "; michael@0: michael@0: var columns = tree.columns; michael@0: var firstcolumn = columns[0]; michael@0: var secondcolumn = columns[1]; michael@0: var lastcolumn = columns[columns.length - 1]; michael@0: var secondlastcolumn = columns[columns.length - 2]; michael@0: var selection = tree.view.selection; michael@0: michael@0: selection.clearSelection(); michael@0: selection.currentIndex = -1; michael@0: selection.currentColumn = firstcolumn; michael@0: is(selection.currentColumn, firstcolumn, testid + " first currentColumn"); michael@0: michael@0: // no selection yet so nothing should happen when the left and right cursor keys are pressed michael@0: synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right no selection"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key right no selection", -1, [], null, firstcolumn); michael@0: michael@0: selection.currentColumn = secondcolumn; michael@0: synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left no selection"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key left no selection", -1, [], null, secondcolumn); michael@0: michael@0: selection.select(2); michael@0: selection.currentIndex = 2; michael@0: if (0) { // XXXndeakin disable these tests for now michael@0: mouseOnCell(tree, 1, secondlastcolumn, "mouse on cell"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "mouse on cell", 1, [1], null, secondlastcolumn); michael@0: } michael@0: michael@0: tree.focus(); michael@0: michael@0: // selection is set, so it should move when the left and right cursor keys are pressed michael@0: tree.treeBoxObject.scrollToRow(0); michael@0: selection.select(1); michael@0: selection.currentIndex = 1; michael@0: selection.currentColumn = secondcolumn; michael@0: synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in second column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key left in second column", 1, [1], 0, firstcolumn); michael@0: michael@0: synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in first column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key left in first column", 1, [1], 0, firstcolumn); michael@0: michael@0: selection.currentColumn = secondlastcolumn; michael@0: synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in second last column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key right in second last column", 1, [1], 0, lastcolumn); michael@0: michael@0: synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in last column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key right in last column", 1, [1], 0, lastcolumn); michael@0: michael@0: synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in second row"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key up in second row", 0, [0], 0, lastcolumn); michael@0: michael@0: synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in first row"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key up in first row", 0, [0], 0, lastcolumn); michael@0: michael@0: synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in first row"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key down in first row", 1, [1], 0, lastcolumn); michael@0: michael@0: var lastidx = tree.view.rowCount - 1; michael@0: tree.treeBoxObject.scrollToRow(lastidx - 3); michael@0: selection.select(lastidx); michael@0: selection.currentIndex = lastidx; michael@0: synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in last row"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key down in last row", lastidx, [lastidx], lastidx - 3, lastcolumn); michael@0: michael@0: synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0, lastcolumn); michael@0: michael@0: synthesizeKeyExpectEvent("VK_END", {}, tree, "!select", "key end"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key end", lastidx, [lastidx], lastidx - 3, lastcolumn); michael@0: michael@0: for (var t = 0; t < 2; t++) { michael@0: var testidmod = (t == 0) ? "" : " rev"; michael@0: michael@0: // scroll to the end, subtract 3 because we want lastidx to appear michael@0: // at the end of view michael@0: tree.treeBoxObject.scrollToRow(lastidx - 3); michael@0: selection.select(lastidx); michael@0: selection.currentIndex = lastidx; michael@0: var expectedrow = tree.pageUpOrDownMovesSelection ? lastidx - 3 : lastidx; michael@0: synthesizeKeyExpectEvent("VK_PAGE_UP", {}, tree, "!select", "key page up"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page up" + testidmod, michael@0: expectedrow, [expectedrow], michael@0: tree.pageUpOrDownMovesSelection ? lastidx - 3 : 0, lastcolumn); michael@0: michael@0: tree.treeBoxObject.scrollToRow(1); michael@0: selection.select(1); michael@0: selection.currentIndex = 1; michael@0: var expectedrow = tree.pageUpOrDownMovesSelection ? 4 : 1; michael@0: synthesizeKeyExpectEvent("VK_PAGE_DOWN", {}, tree, "!select", "key page down"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key page down" + testidmod, michael@0: expectedrow, [expectedrow], michael@0: tree.pageUpOrDownMovesSelection ? 1 : lastidx - 3, lastcolumn); michael@0: michael@0: tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; michael@0: } michael@0: michael@0: // now check navigation when there is unselctable column michael@0: secondcolumn.element.setAttribute("selectable", "false"); michael@0: secondcolumn.invalidate(); michael@0: is(secondcolumn.selectable, false, testid + "set selectable attribute"); michael@0: michael@0: if (columns.length >= 3) { michael@0: selection.select(3); michael@0: selection.currentIndex = 3; michael@0: // check whether unselectable columns are skipped over michael@0: selection.currentColumn = firstcolumn; michael@0: synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key right unselectable column", michael@0: 3, [3], null, secondcolumn.getNext()); michael@0: michael@0: synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key left unselectable column", michael@0: 3, [3], null, firstcolumn); michael@0: } michael@0: michael@0: secondcolumn.element.removeAttribute("selectable"); michael@0: secondcolumn.invalidate(); michael@0: is(secondcolumn.selectable, true, testid + "clear selectable attribute"); michael@0: michael@0: // check to ensure that navigation isn't allowed if the first column is not selectable michael@0: selection.currentColumn = secondcolumn; michael@0: firstcolumn.element.setAttribute("selectable", "false"); michael@0: firstcolumn.invalidate(); michael@0: synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable first column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key left unselectable first column", michael@0: 3, [3], null, secondcolumn); michael@0: firstcolumn.element.removeAttribute("selectable"); michael@0: firstcolumn.invalidate(); michael@0: michael@0: // check to ensure that navigation isn't allowed if the last column is not selectable michael@0: selection.currentColumn = secondlastcolumn; michael@0: lastcolumn.element.setAttribute("selectable", "false"); michael@0: lastcolumn.invalidate(); michael@0: synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable last column"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key right unselectable last column", michael@0: 3, [3], null, secondlastcolumn); michael@0: lastcolumn.element.removeAttribute("selectable"); michael@0: lastcolumn.invalidate(); michael@0: michael@0: // now check for cells with selectable false michael@0: if (!rowInfo.rows[4].cells[1].selectable && columns.length >= 3) { michael@0: // check whether unselectable cells are skipped over michael@0: selection.select(4); michael@0: selection.currentIndex = 4; michael@0: michael@0: selection.currentColumn = firstcolumn; michael@0: synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable cell"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key right unselectable cell", michael@0: 4, [4], null, secondcolumn.getNext()); michael@0: michael@0: synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable cell"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key left unselectable cell", michael@0: 4, [4], null, firstcolumn); michael@0: michael@0: tree.treeBoxObject.scrollToRow(1); michael@0: selection.select(3); michael@0: selection.currentIndex = 3; michael@0: selection.currentColumn = secondcolumn; michael@0: michael@0: synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down unselectable cell"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key down unselectable cell", michael@0: 5, [5], 2, secondcolumn); michael@0: michael@0: tree.treeBoxObject.scrollToRow(4); michael@0: synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up unselectable cell"); michael@0: testtag_tree_TreeSelection_State(tree, testid + "key up unselectable cell", michael@0: 3, [3], 3, secondcolumn); michael@0: } michael@0: michael@0: // restore the scroll position to the start of the page michael@0: sendKey("HOME"); michael@0: } michael@0: michael@0: function testtag_tree_TreeView(tree, testid, rowInfo) michael@0: { michael@0: testid += " view "; michael@0: michael@0: var columns = tree.columns; michael@0: var view = tree.view; michael@0: michael@0: is(view instanceof Components.interfaces.nsITreeView, true, testid + "view is a TreeView"); michael@0: is(view.rowCount, rowInfo.rows.length, testid + "rowCount"); michael@0: michael@0: testtag_tree_TreeView_rows(tree, testid, rowInfo, 0); michael@0: michael@0: // note that this will only work for content trees currently michael@0: view.setCellText(0, columns[1], "Changed Value"); michael@0: is(view.getCellText(0, columns[1]), "Changed Value", "setCellText"); michael@0: michael@0: view.setCellValue(1, columns[0], "Another Changed Value"); michael@0: is(view.getCellValue(1, columns[0]), "Another Changed Value", "setCellText"); michael@0: } michael@0: michael@0: function testtag_tree_TreeView_rows(tree, testid, rowInfo, startRow) michael@0: { michael@0: var r; michael@0: var columns = tree.columns; michael@0: var view = tree.view; michael@0: var length = rowInfo.rows.length; michael@0: michael@0: // methods to test along with the functions which determine the expected value michael@0: var checkRowMethods = michael@0: { michael@0: isContainer: function(row) { return row.container }, michael@0: isContainerOpen: function(row) { return false }, michael@0: isContainerEmpty: function(row) { return (row.children != null && row.children.rows.length == 0) }, michael@0: isSeparator: function(row) { return row.separator }, michael@0: getRowProperties: function(row) { return row.properties }, michael@0: getLevel: function(row) { return row.level }, michael@0: getParentIndex: function(row) { return row.parent }, michael@0: hasNextSibling: function(row) { return r < startRow + length - 1; } michael@0: }; michael@0: michael@0: var checkCellMethods = michael@0: { michael@0: getCellText: function(row, cell) { return cell.label }, michael@0: getCellValue: function(row, cell) { return cell.value }, michael@0: getCellProperties: function(row, cell) { return cell.properties }, michael@0: isEditable: function(row, cell) { return cell.editable }, michael@0: isSelectable: function(row, cell) { return cell.selectable }, michael@0: getImageSrc: function(row, cell) { return cell.image }, michael@0: getProgressMode: function(row, cell) { return cell.mode } michael@0: }; michael@0: michael@0: var failedMethods = { }; michael@0: var checkMethod, actual, expected; michael@0: var containerInfo = null; michael@0: var toggleOpenStateOK = true; michael@0: michael@0: for (r = startRow; r < length; r++) { michael@0: var row = rowInfo.rows[r]; michael@0: for (var c = 0; c < row.cells.length; c++) { michael@0: var cell = row.cells[c]; michael@0: michael@0: for (checkMethod in checkCellMethods) { michael@0: expected = checkCellMethods[checkMethod](row, cell); michael@0: actual = view[checkMethod](r, columns[c]); michael@0: if (actual !== expected) { michael@0: failedMethods[checkMethod] = true; michael@0: is(actual, expected, testid + "row " + r + " column " + c + " " + checkMethod + " is incorrect"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // compare row properties michael@0: for (checkMethod in checkRowMethods) { michael@0: expected = checkRowMethods[checkMethod](row, r); michael@0: if (checkMethod == "hasNextSibling") { michael@0: actual = view[checkMethod](r, r); michael@0: } michael@0: else { michael@0: actual = view[checkMethod](r); michael@0: } michael@0: if (actual !== expected) { michael@0: failedMethods[checkMethod] = true; michael@0: is(actual, expected, testid + "row " + r + " " + checkMethod + " is incorrect"); michael@0: } michael@0: } michael@0: /* michael@0: // open and recurse into containers michael@0: if (row.container) { michael@0: view.toggleOpenState(r); michael@0: if (!view.isContainerOpen(r)) { michael@0: toggleOpenStateOK = false; michael@0: is(view.isContainerOpen(r), true, testid + "row " + r + " toggleOpenState open"); michael@0: } michael@0: testtag_tree_TreeView_rows(tree, testid + "container " + r + " ", row.children, r + 1); michael@0: view.toggleOpenState(r); michael@0: if (view.isContainerOpen(r)) { michael@0: toggleOpenStateOK = false; michael@0: is(view.isContainerOpen(r), false, testid + "row " + r + " toggleOpenState close"); michael@0: } michael@0: } michael@0: */ michael@0: } michael@0: michael@0: for (var failedMethod in failedMethods) { michael@0: if (failedMethod in checkRowMethods) michael@0: delete checkRowMethods[failedMethod]; michael@0: if (failedMethod in checkCellMethods) michael@0: delete checkCellMethods[failedMethod]; michael@0: } michael@0: michael@0: for (checkMethod in checkRowMethods) michael@0: is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod); michael@0: for (checkMethod in checkCellMethods) michael@0: is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod); michael@0: if (toggleOpenStateOK) michael@0: is("toggleOpenState ok", "toggleOpenState ok", testid + "toggleOpenState"); michael@0: } michael@0: michael@0: function testtag_tree_TreeView_rows_sort(tree, testid, rowInfo) michael@0: { michael@0: // check if cycleHeader sorts the columns michael@0: var columnIndex = 0; michael@0: var view = tree.view; michael@0: var column = tree.columns[columnIndex]; michael@0: var columnElement = column.element; michael@0: var sortkey = columnElement.getAttribute("sort"); michael@0: if (sortkey) { michael@0: view.cycleHeader(column); michael@0: is(tree.getAttribute("sort"), sortkey, "cycleHeader sort"); michael@0: is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending"); michael@0: is(columnElement.getAttribute("sortDirection"), "ascending", "cycleHeader column sortDirection"); michael@0: is(columnElement.getAttribute("sortActive"), "true", "cycleHeader column sortActive"); michael@0: view.cycleHeader(column); michael@0: is(tree.getAttribute("sortDirection"), "descending", "cycleHeader sortDirection descending"); michael@0: is(columnElement.getAttribute("sortDirection"), "descending", "cycleHeader column sortDirection descending"); michael@0: view.cycleHeader(column); michael@0: is(tree.getAttribute("sortDirection"), "", "cycleHeader sortDirection natural"); michael@0: is(columnElement.getAttribute("sortDirection"), "", "cycleHeader column sortDirection natural"); michael@0: // XXXndeakin content view isSorted needs to be tested michael@0: } michael@0: michael@0: // Check that clicking on column header sorts the column. michael@0: var columns = getSortedColumnArray(tree); michael@0: is(columnElement.getAttribute("sortDirection"), "", michael@0: "cycleHeader column sortDirection"); michael@0: michael@0: // Click once on column header and check sorting has cycled once. michael@0: mouseClickOnColumnHeader(columns, columnIndex, 0, 1); michael@0: is(columnElement.getAttribute("sortDirection"), "ascending", michael@0: "single click cycleHeader column sortDirection ascending"); michael@0: michael@0: // Now simulate a double click. michael@0: mouseClickOnColumnHeader(columns, columnIndex, 0, 2); michael@0: if (navigator.platform.indexOf("Win") == 0) { michael@0: // Windows cycles only once on double click. michael@0: is(columnElement.getAttribute("sortDirection"), "descending", michael@0: "double click cycleHeader column sortDirection descending"); michael@0: // 1 single clicks should restore natural sorting. michael@0: mouseClickOnColumnHeader(columns, columnIndex, 0, 1); michael@0: } michael@0: michael@0: // Check we have gone back to natural sorting. michael@0: is(columnElement.getAttribute("sortDirection"), "", michael@0: "cycleHeader column sortDirection"); michael@0: michael@0: columnElement.setAttribute("sorthints", "twostate"); michael@0: view.cycleHeader(column); michael@0: is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate"); michael@0: view.cycleHeader(column); michael@0: is(tree.getAttribute("sortDirection"), "descending", "cycleHeader sortDirection ascending twostate"); michael@0: view.cycleHeader(column); michael@0: is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate again"); michael@0: columnElement.removeAttribute("sorthints"); michael@0: view.cycleHeader(column); michael@0: view.cycleHeader(column); michael@0: michael@0: is(columnElement.getAttribute("sortDirection"), "", michael@0: "cycleHeader column sortDirection reset"); michael@0: } michael@0: michael@0: // checks if the current and selected rows are correct michael@0: // current is the index of the current row michael@0: // selected is an array of the indicies of the selected rows michael@0: // column is the selected column michael@0: // viewidx is the row that should be visible at the top of the tree michael@0: function testtag_tree_TreeSelection_State(tree, testid, current, selected, viewidx, column) michael@0: { michael@0: var selection = tree.view.selection; michael@0: michael@0: if (!column) michael@0: column = (tree.selType == "cell") ? tree.columns[0] : null; michael@0: michael@0: is(selection.count, selected.length, testid + " count"); michael@0: is(tree.currentIndex, current, testid + " currentIndex"); michael@0: is(selection.currentIndex, current, testid + " TreeSelection currentIndex"); michael@0: is(selection.currentColumn, column, testid + " currentColumn"); michael@0: if (viewidx !== null && viewidx !== undefined) michael@0: is(tree.treeBoxObject.getFirstVisibleRow(), viewidx, testid + " first visible row"); michael@0: michael@0: var actualSelected = []; michael@0: var count = tree.view.rowCount; michael@0: for (var s = 0; s < count; s++) { michael@0: if (selection.isSelected(s)) michael@0: actualSelected.push(s); michael@0: } michael@0: michael@0: is(compareArrays(selected, actualSelected), true, testid + " selection [" + selected + "]"); michael@0: michael@0: actualSelected = []; michael@0: var rangecount = selection.getRangeCount(); michael@0: for (var r = 0; r < rangecount; r++) { michael@0: var start = {}, end = {}; michael@0: selection.getRangeAt(r, start, end); michael@0: for (var rs = start.value; rs <= end.value; rs++) michael@0: actualSelected.push(rs); michael@0: } michael@0: michael@0: is(compareArrays(selected, actualSelected), true, testid + " range selection [" + selected + "]"); michael@0: } michael@0: michael@0: function testtag_tree_column_reorder() michael@0: { michael@0: // Make sure the tree is scrolled into the view, otherwise the test will michael@0: // fail michael@0: var testframe = window.parent.document.getElementById("testframe"); michael@0: if (testframe) { michael@0: testframe.scrollIntoView(); michael@0: } michael@0: michael@0: var tree = document.getElementById("tree-column-reorder"); michael@0: var numColumns = tree.columns.count; michael@0: michael@0: var reference = []; michael@0: for (var i = 0; i < numColumns; i++) { michael@0: reference.push("col_" + i); michael@0: } michael@0: michael@0: // Drag the first column to each position michael@0: for (var i = 0; i < numColumns - 1; i++) { michael@0: synthesizeColumnDrag(tree, i, i + 1, true); michael@0: arrayMove(reference, i, i + 1, true); michael@0: checkColumns(tree, reference, "drag first column right"); michael@0: } michael@0: michael@0: // And back michael@0: for (var i = numColumns - 1; i >= 1; i--) { michael@0: synthesizeColumnDrag(tree, i, i - 1, false); michael@0: arrayMove(reference, i, i - 1, false); michael@0: checkColumns(tree, reference, "drag last column left"); michael@0: } michael@0: michael@0: // Drag each column one column left michael@0: for (var i = 1; i < numColumns; i++) { michael@0: synthesizeColumnDrag(tree, i, i - 1, false); michael@0: arrayMove(reference, i, i - 1, false); michael@0: checkColumns(tree, reference, "drag each column left"); michael@0: } michael@0: michael@0: // And back michael@0: for (var i = numColumns - 2; i >= 0; i--) { michael@0: synthesizeColumnDrag(tree, i, i + 1, true); michael@0: arrayMove(reference, i, i + 1, true); michael@0: checkColumns(tree, reference, "drag each column right"); michael@0: } michael@0: michael@0: // Drag each column 5 to the right michael@0: for (var i = 0; i < numColumns - 5; i++) { michael@0: synthesizeColumnDrag(tree, i, i + 5, true); michael@0: arrayMove(reference, i, i + 5, true); michael@0: checkColumns(tree, reference, "drag each column 5 to the right"); michael@0: } michael@0: michael@0: // And to the left michael@0: for (var i = numColumns - 6; i >= 5; i--) { michael@0: synthesizeColumnDrag(tree, i, i - 5, false); michael@0: arrayMove(reference, i, i - 5, false); michael@0: checkColumns(tree, reference, "drag each column 5 to the left"); michael@0: } michael@0: michael@0: // Test that moving a column after itself does not move anything michael@0: synthesizeColumnDrag(tree, 0, 0, true); michael@0: checkColumns(tree, reference, "drag to itself"); michael@0: is(document.treecolDragging, null, "drag to itself completed"); michael@0: michael@0: // XXX roc should this be here??? michael@0: SimpleTest.finish(); michael@0: } michael@0: michael@0: function testtag_tree_wheel(aTree) michael@0: { michael@0: const deltaModes = [ michael@0: WheelEvent.DOM_DELTA_PIXEL, // 0 michael@0: WheelEvent.DOM_DELTA_LINE, // 1 michael@0: WheelEvent.DOM_DELTA_PAGE // 2 michael@0: ]; michael@0: function helper(aStart, aDelta, aIntDelta, aDeltaMode) michael@0: { michael@0: aTree.treeBoxObject.scrollToRow(aStart); michael@0: var expected = !aIntDelta ? aStart : michael@0: aDeltaMode != WheelEvent.DOM_DELTA_PAGE ? aStart + aIntDelta : michael@0: aIntDelta > 0 ? aStart + aTree.treeBoxObject.getPageLength() : michael@0: aStart - aTree.treeBoxObject.getPageLength(); michael@0: if (expected < 0) { michael@0: expected = 0; michael@0: } michael@0: if (expected > aTree.view.rowCount - aTree.treeBoxObject.getPageLength()) { michael@0: expected = aTree.view.rowCount - aTree.treeBoxObject.getPageLength(); michael@0: } michael@0: synthesizeWheel(aTree.body, 1, 1, michael@0: { deltaMode: aDeltaMode, deltaY: aDelta, michael@0: lineOrPageDeltaY: aIntDelta }); michael@0: is(aTree.treeBoxObject.getFirstVisibleRow(), expected, michael@0: "testtag_tree_wheel: vertical, starting " + aStart + michael@0: " delta " + aDelta + " lineOrPageDelta " + aIntDelta + michael@0: " aDeltaMode " + aDeltaMode); michael@0: michael@0: aTree.treeBoxObject.scrollToRow(aStart); michael@0: // Check that horizontal scrolling has no effect michael@0: synthesizeWheel(aTree.body, 1, 1, michael@0: { deltaMode: aDeltaMode, deltaX: aDelta, michael@0: lineOrPageDeltaX: aIntDelta }); michael@0: is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, michael@0: "testtag_tree_wheel: horizontal, starting " + aStart + michael@0: " delta " + aDelta + " lineOrPageDelta " + aIntDelta + michael@0: " aDeltaMode " + aDeltaMode); michael@0: } michael@0: michael@0: var defaultPrevented = 0; michael@0: michael@0: function wheelListener(event) { michael@0: defaultPrevented++; michael@0: } michael@0: window.addEventListener("wheel", wheelListener, false); michael@0: michael@0: deltaModes.forEach(function(aDeltaMode) { michael@0: var delta = (aDeltaMode == WheelEvent.DOM_DELTA_PIXEL) ? 5.0 : 0.3; michael@0: helper(2, -delta, 0, aDeltaMode); michael@0: helper(2, -delta, -1, aDeltaMode); michael@0: helper(2, delta, 0, aDeltaMode); michael@0: helper(2, delta, 1, aDeltaMode); michael@0: helper(2, -2 * delta, 0, aDeltaMode); michael@0: helper(2, -2 * delta, -1, aDeltaMode); michael@0: helper(2, 2 * delta, 0, aDeltaMode); michael@0: helper(2, 2 * delta, 1, aDeltaMode); michael@0: }); michael@0: michael@0: window.removeEventListener("wheel", wheelListener, false); michael@0: is(defaultPrevented, 48, "wheel event default prevented"); michael@0: } michael@0: michael@0: function synthesizeColumnDrag(aTree, aMouseDownColumnNumber, aMouseUpColumnNumber, aAfter) michael@0: { michael@0: var columns = getSortedColumnArray(aTree); michael@0: michael@0: var down = columns[aMouseDownColumnNumber].element; michael@0: var up = columns[aMouseUpColumnNumber].element; michael@0: michael@0: // Target the initial mousedown in the middle of the column header so we michael@0: // avoid the extra hit test space given to the splitter michael@0: var columnWidth = down.boxObject.width; michael@0: var splitterHitWidth = columnWidth / 2; michael@0: synthesizeMouse(down, splitterHitWidth, 3, { type: "mousedown"}); michael@0: michael@0: var offsetX = 0; michael@0: if (aAfter) { michael@0: offsetX = columnWidth; michael@0: } michael@0: michael@0: if (aMouseUpColumnNumber > aMouseDownColumnNumber) { michael@0: for (var i = aMouseDownColumnNumber; i <= aMouseUpColumnNumber; i++) { michael@0: var move = columns[i].element; michael@0: synthesizeMouse(move, offsetX, 3, { type: "mousemove"}); michael@0: } michael@0: } michael@0: else { michael@0: for (var i = aMouseDownColumnNumber; i >= aMouseUpColumnNumber; i--) { michael@0: var move = columns[i].element; michael@0: synthesizeMouse(move, offsetX, 3, { type: "mousemove"}); michael@0: } michael@0: } michael@0: michael@0: synthesizeMouse(up, offsetX, 3, { type: "mouseup"}); michael@0: } michael@0: michael@0: function arrayMove(aArray, aFrom, aTo, aAfter) michael@0: { michael@0: var o = aArray.splice(aFrom, 1)[0]; michael@0: if (aTo > aFrom) { michael@0: aTo--; michael@0: } michael@0: michael@0: if (aAfter) { michael@0: aTo++; michael@0: } michael@0: michael@0: aArray.splice(aTo, 0, o); michael@0: } michael@0: michael@0: function getSortedColumnArray(aTree) michael@0: { michael@0: var columns = aTree.columns; michael@0: var a = []; michael@0: for (var i = 0; i < columns.length; i++) { michael@0: a.push(columns.getColumnAt(i)); michael@0: } michael@0: michael@0: a.sort(function(a, b) { michael@0: var o1 = parseInt(a.element.getAttribute("ordinal")); michael@0: var o2 = parseInt(b.element.getAttribute("ordinal")); michael@0: return o1 - o2; michael@0: }); michael@0: return a; michael@0: } michael@0: michael@0: function checkColumns(aTree, aReference, aMessage) michael@0: { michael@0: var columns = getSortedColumnArray(aTree); michael@0: var ids = []; michael@0: columns.forEach(function(e) { michael@0: ids.push(e.element.id); michael@0: }); michael@0: is(compareArrays(ids, aReference), true, aMessage); michael@0: } michael@0: michael@0: function mouseOnCell(tree, row, column, testname) michael@0: { michael@0: var x = {}, y = {}, width = {}, height = {}; michael@0: tree.boxObject.getCoordsForCellItem(row, column, "text", x, y, width, height); michael@0: michael@0: synthesizeMouseExpectEvent(tree.body, x.value, y.value, {}, tree, "select", testname); michael@0: } michael@0: michael@0: function mouseClickOnColumnHeader(aColumns, aColumnIndex, aButton, aClickCount) michael@0: { michael@0: var columnHeader = aColumns[aColumnIndex].element; michael@0: var columnHeaderRect = columnHeader.getBoundingClientRect(); michael@0: var columnWidth = columnHeaderRect.right - columnHeaderRect.left; michael@0: // For multiple click we send separate click events, with increasing michael@0: // clickCount. This simulates the common behavior of multiple clicks. michael@0: for (var i = 1; i <= aClickCount; i++) { michael@0: // Target the middle of the column header. michael@0: synthesizeMouse(columnHeader, columnWidth / 2, 3, michael@0: { button: aButton, michael@0: clickCount: i }, null); michael@0: } michael@0: } michael@0: michael@0: function mouseDblClickOnCell(tree, row, column, testname) michael@0: { michael@0: // select the row we will edit michael@0: var selection = tree.view.selection; michael@0: selection.select(row); michael@0: tree.treeBoxObject.ensureRowIsVisible(row); michael@0: michael@0: // get cell coordinates michael@0: var x = {}, y = {}, width = {}, height = {}; michael@0: tree.treeBoxObject.getCoordsForCellItem(row, column, "text", x, y, width, height); michael@0: michael@0: synthesizeMouse(tree.body, x.value, y.value, { clickCount: 2 }, null); michael@0: } michael@0: michael@0: function compareArrays(arr1, arr2) michael@0: { michael@0: if (arr1.length != arr2.length) michael@0: return false; michael@0: michael@0: for (var i = 0; i < arr1.length; i++) { michael@0: if (arr1[i] != arr2[i]) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: function convertProperties(arr) michael@0: { michael@0: var results = []; michael@0: var count = arr.Count(); michael@0: for (var i = 0; i < count; i++) michael@0: results.push(arr.GetElementAt(i).QueryInterface(Components.interfaces.nsIAtom).toString()); michael@0: michael@0: results.sort(); michael@0: return results.join(" "); michael@0: } michael@0: michael@0: function convertDOMtoTreeRowInfo(treechildren, level, rowidx) michael@0: { michael@0: var obj = { rows: [] }; michael@0: michael@0: var parentidx = rowidx.value; michael@0: michael@0: treechildren = treechildren.childNodes; michael@0: for (var r = 0; r < treechildren.length; r++) { michael@0: rowidx.value++; michael@0: michael@0: var treeitem = treechildren[r]; michael@0: if (treeitem.hasChildNodes()) { michael@0: var treerow = treeitem.firstChild; michael@0: var cellInfo = []; michael@0: for (var c = 0; c < treerow.childNodes.length; c++) { michael@0: var cell = treerow.childNodes[c]; michael@0: cellInfo.push({ label: "" + cell.getAttribute("label"), michael@0: value: cell.getAttribute("value"), michael@0: properties: cell.getAttribute("properties"), michael@0: editable: cell.getAttribute("editable") != "false", michael@0: selectable: cell.getAttribute("selectable") != "false", michael@0: image: cell.getAttribute("src"), michael@0: mode: cell.hasAttribute("mode") ? parseInt(cell.getAttribute("mode")) : 3 }); michael@0: } michael@0: michael@0: var descendants = treeitem.lastChild; michael@0: var children = (treerow == descendants) ? null : michael@0: convertDOMtoTreeRowInfo(descendants, level + 1, rowidx); michael@0: obj.rows.push({ cells: cellInfo, michael@0: properties: treerow.getAttribute("properties"), michael@0: container: treeitem.getAttribute("container") == "true", michael@0: separator: treeitem.localName == "treeseparator", michael@0: children: children, michael@0: level: level, michael@0: parent: parentidx }); michael@0: } michael@0: } michael@0: michael@0: return obj; michael@0: }