Bug 498155 -- open folder in new tab is labeled <blank> (when load tabs in foreground is set). Fix and add tests. r=bienvenu+asuth, blocking-tb3b3+
authorSiddharth Agarwal <sid.bugzilla@gmail.com>
Wed, 08 Jul 2009 19:36:59 +0530
changeset 3040 e33a0b494e4d8adf68f404ad8c21d50b1254a96b
parent 3039 349adc10628799e4ff53a584533bafdaa99dda96
child 3041 5bb1a26308abf4593d846fc67f8ae2d10f9ccd29
push idunknown
push userunknown
push dateunknown
reviewersbienvenu
bugs498155
Bug 498155 -- open folder in new tab is labeled <blank> (when load tabs in foreground is set). Fix and add tests. r=bienvenu+asuth, blocking-tb3b3+
mail/base/content/folderPane.js
mail/base/content/mailWindowOverlay.js
mail/test/mozmill/folder-display/test-right-click-middle-click-folders.js
mail/test/mozmill/folder-display/test-right-click-middle-click-messages.js
mail/test/mozmill/folder-display/test-right-click-middle-click.js
mail/test/mozmill/shared-modules/test-folder-display-helpers.js
mail/test/mozmill/shared-modules/test-window-helpers.js
--- a/mail/base/content/folderPane.js
+++ b/mail/base/content/folderPane.js
@@ -304,16 +304,28 @@ let gFolderTreeView = {
     for (let i in this._rowMap) {
       if (this._rowMap[i].id == aFolder.URI)
         return i;
     }
     return null;
   },
 
   /**
+   * Returns the folder for an index in the current display.
+   *
+   * @param aIndex the index for which the folder should be returned.
+   * @note If the index is out of bounds, this function returns null.
+   */
+  getFolderForIndex: function ftv_getFolderForIndex(aIndex) {
+    if (aIndex < 0 || aIndex >= this._rowMap.length)
+      return null;
+    return this._rowMap[aIndex]._folder;
+  },
+
+  /**
    * Returns an array of nsIMsgFolders corresponding to the current selection
    * in the tree
    */
   getSelectedFolders: function ftv_getSelectedFolders() {
     let folderArray = [];
     let selection = this._treeElement.view.selection;
     let rangeCount = selection.getRangeCount();
     for (let i = 0; i < rangeCount; i++) {
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -1744,25 +1744,27 @@ let mailTabType = {
 
         this.openTab(aTab, false,
                      new MessagePaneDisplayWidget(messagePaneShouldBeVisible));
 
         let background = ("background" in aArgs) && aArgs.background;
         let msgHdr = ("msgHdr" in aArgs) && aArgs.msgHdr;
 
         if (!background) {
+          aTab.folderDisplay.makeActive();
+          // HACK: Since we've switched away from the tab, we need to bring
+          // back the real selection before selecting the folder, so do that
+          RestoreSelectionWithoutContentLoad(document.getElementById(
+                                                 "folderTree"));
+
           // Clear selection, because context clicking on a folder and opening in a
-          // new tab needs to have SelectFolder think the selection has changed.
-          // We also need to clear these globals to subvert the code that prevents
-          // folder loads when things haven't changed.
-          let folderTree = document.getElementById("folderTree");
-          folderTree.view.selection.clearSelection();
-          folderTree.view.selection.currentIndex = -1;
-
-          aTab.folderDisplay.makeActive();
+          // new tab needs to have selectFolder think the selection has changed.
+          gFolderTreeView.selection.clearSelection();
+          gFolderTreeView.selection.currentIndex = -1;
+
           // This will call aTab.folderDisplay.show(aArgs.folder), which takes
           // care of the tab title and other stuff
           gFolderTreeView.selectFolder(aArgs.folder);
           if (msgHdr)
             aTab.folderDisplay.selectMessage(msgHdr);
         }
         else {
           // Since we can't call gFolderTreeView.selectFolder, we need to make the folder
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-right-click-middle-click-folders.js
@@ -0,0 +1,291 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *   Siddharth Agarwal <sid.bugzilla@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test the many horrors involving right-clicks, middle clicks, and
+ * selections... on folders!
+ */
+
+var MODULE_NAME = 'test-right-click-middle-click-folders';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+var folderA, folderB, folderC;
+
+function setupModule(module) {
+  let fdh = collector.getModule('folder-display-helpers');
+  fdh.installInto(module);
+  let wh = collector.getModule('window-helpers');
+  wh.installInto(module);
+
+  folderA = create_folder("RightClickMiddleClickFoldersA");
+  folderB = create_folder("RightClickMiddleClickFoldersB");
+  folderC = create_folder("RightClickMiddleClickFoldersC");
+
+  // We aren't really interested in the messages the folders contain, but just
+  // for appearance's sake, add a message to each folder
+
+  make_new_sets_in_folder(folderA, [{count: 1}]);
+  make_new_sets_in_folder(folderB, [{count: 1}]);
+  make_new_sets_in_folder(folderC, [{count: 1}]);
+}
+
+/**
+ * Make sure that a right-click when there is nothing currently selected does
+ *  not cause us to display something, as well as correctly causing a transient
+ *  selection to occur.
+ */
+function test_right_click_folder_with_nothing_selected() {
+  // This should cause folderA to be displayed
+  be_in_folder(folderA);
+
+  select_no_folders();
+  assert_no_folders_selected();
+
+  right_click_on_folder(folderB);
+  assert_folder_selected(folderB);
+  // The displayed folder shouldn't change
+  assert_folder_displayed(folderA);
+
+  close_popup();
+  assert_no_folders_selected();
+}
+
+/**
+ * One-thing selected, right-click on something else.
+ */
+function test_right_click_folder_with_one_thing_selected() {
+  select_click_folder(folderB);
+  assert_folder_selected_and_displayed(folderB);
+
+  right_click_on_folder(folderA);
+  assert_folder_selected(folderA);
+  assert_folder_displayed(folderB);
+
+  close_popup();
+  assert_folder_selected_and_displayed(folderB);
+}
+
+/**
+ * Many things selected, right-click on something that is not in that selection.
+ */
+function test_right_click_folder_with_many_things_selected() {
+  select_click_folder(folderA);
+  select_shift_click_folder(folderB);
+  assert_folders_selected_and_displayed(folderA, folderB);
+
+  right_click_on_folder(folderC);
+  assert_folder_selected(folderC);
+  assert_folder_displayed(folderA);
+
+  close_popup();
+  assert_folders_selected_and_displayed(folderA, folderB);
+}
+
+/**
+ * One thing selected, right-click on that.
+ */
+function test_right_click_folder_on_existing_single_selection() {
+  select_click_folder(folderA);
+  assert_folders_selected_and_displayed(folderA);
+
+  right_click_on_folder(folderA);
+  assert_folders_selected_and_displayed(folderA);
+
+  close_popup();
+  assert_folders_selected_and_displayed(folderA);
+}
+
+/**
+ * Many things selected, right-click somewhere in the selection.
+ */
+function test_right_click_folder_on_existing_multi_selection() {
+  select_click_folder(folderB);
+  select_shift_click_folder(folderC);
+  assert_folders_selected_and_displayed(folderB, folderC);
+
+  right_click_on_folder(folderC);
+  assert_folders_selected_and_displayed(folderB, folderC);
+
+  close_popup();
+  assert_folders_selected_and_displayed(folderB, folderC);
+}
+
+/**
+ * Middle clicking should open a message in a tab, but not affect our selection.
+ */
+function _middle_click_folder_with_nothing_selected_helper(aBackground) {
+  // This should cause folderA to be displayed
+  be_in_folder(folderA);
+
+  select_no_folders();
+  assert_no_folders_selected();
+
+  let originalTab = mc.tabmail.currentTabInfo;
+  let [newTab, ] = middle_click_on_folder(folderA);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(originalTab);
+    // Now switch to the new tab and check
+    switch_tab(newTab);
+  }
+  assert_folder_selected_and_displayed(folderA);
+  close_tab(newTab);
+
+  // XXX This is wrong, we shouldn't have anything selected. Since we don't
+  // have a special state for nothing selected, we're giving this a pass for
+  // now.
+  assert_folder_selected_and_displayed(folderA);
+}
+
+/**
+ * One-thing selected, middle-click on something else.
+ */
+function _middle_click_folder_with_one_thing_selected_helper(aBackground) {
+  select_click_folder(folderB);
+  assert_folder_selected_and_displayed(folderB);
+
+  let originalTab = mc.tabmail.currentTabInfo;
+  let [newTab, ] = middle_click_on_folder(folderA);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(originalTab);
+    // Now switch to the new tab and check
+    switch_tab(newTab);
+  }
+  assert_folder_selected_and_displayed(folderA);
+  close_tab(newTab);
+
+  assert_folder_selected_and_displayed(folderB);
+}
+
+function _middle_click_folder_with_many_things_selected_helper(aBackground) {
+  select_click_folder(folderB);
+  select_shift_click_folder(folderC);
+  assert_folders_selected_and_displayed(folderB, folderC);
+
+  let originalTab = mc.tabmail.currentTabInfo;
+  let [newTab, ] = middle_click_on_folder(folderA);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(originalTab);
+    // Now switch to the new tab and check
+    switch_tab(newTab);
+  }
+  assert_folder_selected_and_displayed(folderA);
+  close_tab(newTab);
+
+  // XXX Again, this is wrong. We're still giving it a pass because selecting
+  // both folderB and folderC is currently the same as selecting folderB.
+  assert_folder_selected_and_displayed(folderB);
+}
+
+/**
+ * One thing selected, middle-click on that.
+ */
+function _middle_click_folder_on_existing_single_selection_helper(aBackground) {
+  select_click_folder(folderC);
+  assert_folder_selected_and_displayed(folderC);
+
+  let originalTab = mc.tabmail.currentTabInfo;
+  let [newTab, ] = middle_click_on_folder(folderC);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(originalTab);
+    // Now switch to the new tab and check
+    switch_tab(newTab);
+  }
+  assert_folder_selected_and_displayed(folderC);
+  close_tab(newTab);
+
+  assert_folder_selected_and_displayed(folderC);
+}
+
+/**
+ * Many things selected, middle-click somewhere in the selection.
+ */
+function _middle_click_on_existing_multi_selection_helper(aBackground) {
+  select_click_folder(folderA);
+  select_shift_click_folder(folderC);
+  assert_folders_selected_and_displayed(folderA, folderB, folderC);
+
+  let originalTab = mc.tabmail.currentTabInfo;
+  let [newTab, ] = middle_click_on_folder(folderB);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(originalTab);
+    // Now switch to the new tab and check
+    switch_tab(newTab);
+  }
+  assert_folder_selected_and_displayed(folderB);
+  close_tab(newTab);
+
+  // XXX Again, this is wrong. We're still giving it a pass because selecting
+  // folderA through folderC is currently the same as selecting folderA.
+  assert_folder_selected_and_displayed(folderA);
+}
+
+/**
+ * Generate background and foreground tests for each middle click test.
+ *
+ * @param aTests an array of test names
+ */
+function _generate_background_foreground_tests(aTests) {
+  let self = this;
+  for each (let [, test] in Iterator(aTests)) {
+    let helperFunc = this["_" + test + "_helper"];
+    this["test_" + test + "_background"] = function() {
+      set_context_menu_background_tabs(true);
+      helperFunc.apply(self, [true]);
+      reset_context_menu_background_tabs();
+    };
+    this["test_" + test + "_foreground"] = function() {
+      set_context_menu_background_tabs(false);
+      helperFunc.apply(self, [false]);
+      reset_context_menu_background_tabs();
+    };
+  }
+}
+
+_generate_background_foreground_tests([
+  "middle_click_folder_with_nothing_selected",
+  "middle_click_folder_with_one_thing_selected",
+  "middle_click_folder_with_many_things_selected",
+  "middle_click_folder_on_existing_single_selection"
+]);
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/folder-display/test-right-click-middle-click-messages.js
@@ -0,0 +1,452 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Thunderbird Mail Client.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Messaging, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Andrew Sutherland <asutherland@asutherland.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test the many horrors involving right-clicks, middle clicks, and selections.
+ */
+
+var MODULE_NAME = 'test-right-click-middle-click-messages';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
+
+var folder, threadedFolder;
+
+/**
+ * The number of messages in the thread we use to test.
+ */
+var NUM_MESSAGES_IN_THREAD = 6;
+
+function setupModule(module) {
+  let fdh = collector.getModule('folder-display-helpers');
+  fdh.installInto(module);
+  let wh = collector.getModule('window-helpers');
+  wh.installInto(module);
+
+  folder = create_folder("RightClickMiddleClickA");
+  threadedFolder = create_folder("RightClickMiddleClickB");
+  // we want exactly as many messages as we plan to delete, so that we can test
+  //  that the message window and tabs close when they run out of things to
+  //  to display.
+  make_new_sets_in_folder(folder, [{count: 20}]);
+  // Create a few messages and one thread (the order is important here, as it
+  // determines where the thread is placed. We want it placed right at the
+  // end.)
+  make_new_sets_in_folder(threadedFolder, [{count: 50}]);
+  let thread = create_thread(NUM_MESSAGES_IN_THREAD);
+  add_sets_to_folders([threadedFolder], [thread]);
+}
+
+/**
+ * Make sure that a right-click when there is nothing currently selected does
+ *  not cause us to display something, as well as correctly causing a transient
+ *  selection to occur.
+ */
+function test_right_click_with_nothing_selected() {
+  be_in_folder(folder);
+
+  select_none();
+  assert_nothing_selected();
+
+  right_click_on_row(1);
+  assert_selected(1);
+  assert_displayed();
+
+  close_popup();
+  assert_nothing_selected();
+}
+
+/**
+ * One-thing selected, right-click on something else.
+ */
+function test_right_click_with_one_thing_selected() {
+  be_in_folder(folder);
+
+  select_click_row(0);
+  assert_selected_and_displayed(0);
+
+  right_click_on_row(1);
+  assert_selected(1);
+  assert_displayed(0);
+
+  close_popup();
+  assert_selected_and_displayed(0);
+}
+
+/**
+ * Many things selected, right-click on something that is not in that selection.
+ */
+function test_right_click_with_many_things_selected() {
+  be_in_folder(folder);
+
+  select_click_row(0);
+  select_shift_click_row(5);
+  assert_selected_and_displayed([0, 5]);
+
+  right_click_on_row(6);
+  assert_selected(6);
+  assert_displayed([0, 5]);
+
+  close_popup();
+  assert_selected_and_displayed([0, 5]);
+}
+
+/**
+ * One thing selected, right-click on that.
+ */
+function test_right_click_on_existing_single_selection() {
+  be_in_folder(folder);
+
+  select_click_row(3);
+  assert_selected_and_displayed(3);
+
+  right_click_on_row(3);
+  assert_selected_and_displayed(3);
+
+  close_popup();
+  assert_selected_and_displayed(3);
+}
+
+/**
+ * Many things selected, right-click somewhere in the selection.
+ */
+function test_right_click_on_existing_multi_selection() {
+  be_in_folder(folder);
+
+  select_click_row(3);
+  select_shift_click_row(6);
+  assert_selected_and_displayed([3, 6]);
+
+  right_click_on_row(5);
+  assert_selected_and_displayed([3, 6]);
+
+  close_popup();
+  assert_selected_and_displayed([3, 6]);
+}
+
+/**
+ * Middle clicking should open a message in a tab, but not affect our selection.
+ */
+function _middle_click_with_nothing_selected_helper(aBackground) {
+  be_in_folder(folder);
+
+  select_none();
+  assert_nothing_selected();
+  let folderTab = mc.tabmail.currentTabInfo;
+  let [tabMessage, curMessage] = middle_click_on_row(1);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
+  assert_selected_and_displayed(curMessage);
+  close_tab(tabMessage);
+
+  assert_nothing_selected();
+}
+
+/**
+ * One-thing selected, middle-click on something else.
+ */
+function _middle_click_with_one_thing_selected_helper(aBackground) {
+  be_in_folder(folder);
+
+  select_click_row(0);
+  assert_selected_and_displayed(0);
+
+  let folderTab = mc.tabmail.currentTabInfo;
+  let [tabMessage, curMessage] = middle_click_on_row(1);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
+  assert_selected_and_displayed(curMessage);
+  close_tab(tabMessage);
+
+  assert_selected_and_displayed(0);
+}
+
+/**
+ * Many things selected, middle-click on something that is not in that
+ *  selection.
+ */
+function _middle_click_with_many_things_selected_helper(aBackground) {
+  be_in_folder(folder);
+
+  select_click_row(0);
+  select_shift_click_row(5);
+  assert_selected_and_displayed([0, 5]);
+
+  let folderTab = mc.tabmail.currentTabInfo;
+  let [tabMessage, curMessage] = middle_click_on_row(1);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
+  assert_selected_and_displayed(curMessage);
+  close_tab(tabMessage);
+
+  assert_selected_and_displayed([0, 5]);
+}
+
+/**
+ * One thing selected, middle-click on that.
+ */
+function _middle_click_on_existing_single_selection_helper(aBackground) {
+  be_in_folder(folder);
+
+  select_click_row(3);
+  assert_selected_and_displayed(3);
+
+  let folderTab = mc.tabmail.currentTabInfo;
+  let [tabMessage, curMessage] = middle_click_on_row(3);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
+  assert_selected_and_displayed(curMessage);
+  close_tab(tabMessage);
+
+  assert_selected_and_displayed(3);
+}
+
+/**
+ * Many things selected, middle-click somewhere in the selection.
+ */
+function _middle_click_on_existing_multi_selection_helper(aBackground) {
+  be_in_folder(folder);
+
+  select_click_row(3);
+  select_shift_click_row(6);
+  assert_selected_and_displayed([3, 6]);
+
+  let folderTab = mc.tabmail.currentTabInfo;
+  let [tabMessage, curMessage] = middle_click_on_row(5);
+  if (aBackground) {
+    // Make sure we haven't switched to the new tab.
+    assert_selected_tab(folderTab);
+    // Now switch to the new tab and check
+    switch_tab(tabMessage);
+  }
+  assert_selected_and_displayed(curMessage);
+  close_tab(tabMessage);
+
+  assert_selected_and_displayed([3, 6]);
+}
+
+/**
+ * Middle-click on the root of a collapsed thread, making sure that we don't
+ * jump around in the thread tree.
+ */
+function _middle_click_on_collapsed_thread_root_helper(aBackground) {
+  be_in_folder(threadedFolder);
+  make_display_threaded();
+  collapse_all_threads();
+
+  let folderTab = mc.tabmail.currentTabInfo;
+
+  let treeBox = mc.threadTree.treeBoxObject;
+  // Scroll to the top, then to the bottom
+  treeBox.ensureRowIsVisible(0);
+  treeBox.scrollByLines(mc.folderDisplay.view.dbView.rowCount);
+  // Note the first visible row
+  let preFirstRow = treeBox.getFirstVisibleRow();
+  // Middle-click on the root of the collapsed thread, which is also the last
+  // row
+  let [tabMessage, ] = middle_click_on_row(
+                           mc.folderDisplay.view.dbView.rowCount - 1);
+
+  if (!aBackground)
+    // Switch back to the folder tab
+    switch_tab(folderTab);
+
+  // Make sure the first visible row is still the same
+  if (treeBox.getFirstVisibleRow() != preFirstRow)
+    throw new Error("The first visible row should have been " + preFirstRow +
+        ", but is actually " + treeBox.getFirstVisibleRow() + ".");
+
+  close_tab(tabMessage);
+}
+
+/**
+ * Middle-click on the root of an expanded thread, making sure that we don't
+ * jump around in the thread tree.
+ */
+function _middle_click_on_expanded_thread_root_helper(aBackground) {
+  be_in_folder(threadedFolder);
+  make_display_threaded();
+  expand_all_threads();
+
+  let folderTab = mc.tabmail.currentTabInfo;
+
+  let treeBox = mc.threadTree.treeBoxObject;
+  // Scroll to the top, then to near (but not exactly) the bottom
+  treeBox.ensureRowIsVisible(0);
+  treeBox.scrollToRow(mc.folderDisplay.view.dbView.rowCount -
+      treeBox.getPageLength() - (NUM_MESSAGES_IN_THREAD / 2));
+  // Note the first visible row
+  let preFirstRow = treeBox.getFirstVisibleRow();
+  // Middle-click on the root of the expanded thread, which is the row with
+  // index (number of rows - number of messages in thread).
+  let [tabMessage, ] = middle_click_on_row(
+      mc.folderDisplay.view.dbView.rowCount - NUM_MESSAGES_IN_THREAD);
+
+  if (!aBackground)
+    // Switch back to the folder tab
+    switch_tab(folderTab);
+
+  // Make sure the first visible row is still the same
+  if (treeBox.getFirstVisibleRow() != preFirstRow)
+    throw new Error("The first visible row should have been " + preFirstRow +
+        ", but is actually " + treeBox.getFirstVisibleRow() + ".");
+
+  close_tab(tabMessage);
+}
+
+/**
+ * Generate background and foreground tests for each middle click test.
+ *
+ * @param aTests an array of test names
+ */
+function _generate_background_foreground_tests(aTests) {
+  let self = this;
+  for each (let [, test] in Iterator(aTests)) {
+    let helperFunc = this["_" + test + "_helper"];
+    this["test_" + test + "_background"] = function() {
+      set_context_menu_background_tabs(true);
+      helperFunc.apply(self, [true]);
+      reset_context_menu_background_tabs();
+    };
+    this["test_" + test + "_foreground"] = function() {
+      set_context_menu_background_tabs(false);
+      helperFunc.apply(self, [false]);
+      reset_context_menu_background_tabs();
+    };
+  }
+}
+
+_generate_background_foreground_tests([
+  "middle_click_with_nothing_selected",
+  "middle_click_with_one_thing_selected",
+  "middle_click_with_many_things_selected",
+  "middle_click_on_existing_single_selection",
+  "middle_click_on_existing_multi_selection",
+  "middle_click_on_collapsed_thread_root",
+  "middle_click_on_expanded_thread_root"
+]);
+
+/**
+ * Right-click on something and delete it, having no selection previously.
+ */
+function test_right_click_deletion_nothing_selected() {
+  be_in_folder(folder);
+
+  select_none();
+  assert_selected_and_displayed();
+
+  let delMessage = right_click_on_row(3);
+  delete_via_popup();
+  // eh, might as well make sure the deletion worked while we are here
+  assert_message_not_in_view(delMessage);
+
+  assert_selected_and_displayed();
+}
+
+/**
+ * We want to make sure that the selection post-delete still includes the same
+ *  message (and that it is displayed).  In order for this to be interesting,
+ *  we want to make sure that we right-click delete a message above the selected
+ *  message so there is a shift in row numbering.
+ */
+function test_right_click_deletion_one_other_thing_selected() {
+  be_in_folder(folder);
+
+  let curMessage = select_click_row(5);
+
+  let delMessage = right_click_on_row(3);
+  delete_via_popup();
+  assert_message_not_in_view(delMessage);
+
+  assert_selected_and_displayed(curMessage);
+}
+
+function test_right_click_deletion_many_other_things_selected() {
+  be_in_folder(folder);
+
+  select_click_row(4);
+  let messages = select_shift_click_row(6);
+
+  let delMessage = right_click_on_row(2);
+  delete_via_popup();
+  assert_message_not_in_view(delMessage);
+
+  assert_selected_and_displayed(messages);
+}
+
+function test_right_click_deletion_of_one_selected_thing() {
+  be_in_folder(folder);
+
+  let curMessage = select_click_row(2);
+
+  right_click_on_row(2);
+  delete_via_popup();
+  assert_message_not_in_view(curMessage);
+
+  if (!mc.folderDisplay.selectedCount)
+    throw new Error("We should have tried to select something!");
+}
+
+function test_right_click_deletion_of_many_selected_things() {
+  be_in_folder(folder);
+
+  select_click_row(2);
+  let messages = select_shift_click_row(4);
+
+  right_click_on_row(3);
+  delete_via_popup();
+  assert_messages_not_in_view(messages);
+
+  if (!mc.folderDisplay.selectedCount)
+    throw new Error("We should have tried to select something!");
+}
deleted file mode 100644
--- a/mail/test/mozmill/folder-display/test-right-click-middle-click.js
+++ /dev/null
@@ -1,452 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Thunderbird Mail Client.
- *
- * The Initial Developer of the Original Code is
- * Mozilla Messaging, Inc.
- * Portions created by the Initial Developer are Copyright (C) 2009
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Andrew Sutherland <asutherland@asutherland.org>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/*
- * Test the many horrors involving right-clicks, middle clicks, and selections.
- */
-
-var MODULE_NAME = 'test-deletion-with-multiple-displays';
-
-var RELATIVE_ROOT = '../shared-modules';
-var MODULE_REQUIRES = ['folder-display-helpers', 'window-helpers'];
-
-var folder, threadedFolder;
-
-/**
- * The number of messages in the thread we use to test.
- */
-var NUM_MESSAGES_IN_THREAD = 6;
-
-function setupModule(module) {
-  let fdh = collector.getModule('folder-display-helpers');
-  fdh.installInto(module);
-  let wh = collector.getModule('window-helpers');
-  wh.installInto(module);
-
-  folder = create_folder("RightClickMiddleClickA");
-  threadedFolder = create_folder("RightClickMiddleClickB");
-  // we want exactly as many messages as we plan to delete, so that we can test
-  //  that the message window and tabs close when they run out of things to
-  //  to display.
-  make_new_sets_in_folder(folder, [{count: 20}]);
-  // Create a few messages and one thread (the order is important here, as it
-  // determines where the thread is placed. We want it placed right at the
-  // end.)
-  make_new_sets_in_folder(threadedFolder, [{count: 50}]);
-  let thread = create_thread(NUM_MESSAGES_IN_THREAD);
-  add_sets_to_folders([threadedFolder], [thread]);
-}
-
-/**
- * Make sure that a right-click when there is nothing currently selected does
- *  not cause us to display something, as well as correctly causing a transient
- *  selection to occur.
- */
-function test_right_click_with_nothing_selected() {
-  be_in_folder(folder);
-
-  select_none();
-  assert_nothing_selected();
-
-  right_click_on_row(1);
-  assert_selected(1);
-  assert_displayed();
-
-  close_popup();
-  assert_nothing_selected();
-}
-
-/**
- * One-thing selected, right-click on something else.
- */
-function test_right_click_with_one_thing_selected() {
-  be_in_folder(folder);
-
-  select_click_row(0);
-  assert_selected_and_displayed(0);
-
-  right_click_on_row(1);
-  assert_selected(1);
-  assert_displayed(0);
-
-  close_popup();
-  assert_selected_and_displayed(0);
-}
-
-/**
- * Many things selected, right-click on something that is not in that selection.
- */
-function test_right_click_with_many_things_selected() {
-  be_in_folder(folder);
-
-  select_click_row(0);
-  select_shift_click_row(5);
-  assert_selected_and_displayed([0, 5]);
-
-  right_click_on_row(6);
-  assert_selected(6);
-  assert_displayed([0, 5]);
-
-  close_popup();
-  assert_selected_and_displayed([0, 5]);
-}
-
-/**
- * One thing selected, right-click on that.
- */
-function test_right_click_on_existing_single_selection() {
-  be_in_folder(folder);
-
-  select_click_row(3);
-  assert_selected_and_displayed(3);
-
-  right_click_on_row(3);
-  assert_selected_and_displayed(3);
-
-  close_popup();
-  assert_selected_and_displayed(3);
-}
-
-/**
- * Many things selected, right-click somewhere in the selection.
- */
-function test_right_click_on_existing_multi_selection() {
-  be_in_folder(folder);
-
-  select_click_row(3);
-  select_shift_click_row(6);
-  assert_selected_and_displayed([3, 6]);
-
-  right_click_on_row(5);
-  assert_selected_and_displayed([3, 6]);
-
-  close_popup();
-  assert_selected_and_displayed([3, 6]);
-}
-
-/**
- * Middle clicking should open a message in a tab, but not affect our selection.
- */
-function _middle_click_with_nothing_selected_helper(aBackground) {
-  be_in_folder(folder);
-
-  select_none();
-  assert_nothing_selected();
-  let folderTab = mc.tabmail.currentTabInfo;
-  let [tabMessage, curMessage] = middle_click_on_row(1);
-  if (aBackground) {
-    // Make sure we haven't switched to the new tab.
-    assert_selected_tab(folderTab);
-    // Now switch to the new tab and check
-    switch_tab(tabMessage);
-  }
-  assert_selected_and_displayed(curMessage);
-  close_tab(tabMessage);
-
-  assert_nothing_selected();
-}
-
-/**
- * One-thing selected, middle-click on something else.
- */
-function _middle_click_with_one_thing_selected_helper(aBackground) {
-  be_in_folder(folder);
-
-  select_click_row(0);
-  assert_selected_and_displayed(0);
-
-  let folderTab = mc.tabmail.currentTabInfo;
-  let [tabMessage, curMessage] = middle_click_on_row(1);
-  if (aBackground) {
-    // Make sure we haven't switched to the new tab.
-    assert_selected_tab(folderTab);
-    // Now switch to the new tab and check
-    switch_tab(tabMessage);
-  }
-  assert_selected_and_displayed(curMessage);
-  close_tab(tabMessage);
-
-  assert_selected_and_displayed(0);
-}
-
-/**
- * Many things selected, middle-click on something that is not in that
- *  selection.
- */
-function _middle_click_with_many_things_selected_helper(aBackground) {
-  be_in_folder(folder);
-
-  select_click_row(0);
-  select_shift_click_row(5);
-  assert_selected_and_displayed([0, 5]);
-
-  let folderTab = mc.tabmail.currentTabInfo;
-  let [tabMessage, curMessage] = middle_click_on_row(1);
-  if (aBackground) {
-    // Make sure we haven't switched to the new tab.
-    assert_selected_tab(folderTab);
-    // Now switch to the new tab and check
-    switch_tab(tabMessage);
-  }
-  assert_selected_and_displayed(curMessage);
-  close_tab(tabMessage);
-
-  assert_selected_and_displayed([0, 5]);
-}
-
-/**
- * One thing selected, middle-click on that.
- */
-function _middle_click_on_existing_single_selection_helper(aBackground) {
-  be_in_folder(folder);
-
-  select_click_row(3);
-  assert_selected_and_displayed(3);
-
-  let folderTab = mc.tabmail.currentTabInfo;
-  let [tabMessage, curMessage] = middle_click_on_row(3);
-  if (aBackground) {
-    // Make sure we haven't switched to the new tab.
-    assert_selected_tab(folderTab);
-    // Now switch to the new tab and check
-    switch_tab(tabMessage);
-  }
-  assert_selected_and_displayed(curMessage);
-  close_tab(tabMessage);
-
-  assert_selected_and_displayed(3);
-}
-
-/**
- * Many things selected, right-click somewhere in the selection.
- */
-function _middle_click_on_existing_multi_selection_helper(aBackground) {
-  be_in_folder(folder);
-
-  select_click_row(3);
-  select_shift_click_row(6);
-  assert_selected_and_displayed([3, 6]);
-
-  let folderTab = mc.tabmail.currentTabInfo;
-  let [tabMessage, curMessage] = middle_click_on_row(5);
-  if (aBackground) {
-    // Make sure we haven't switched to the new tab.
-    assert_selected_tab(folderTab);
-    // Now switch to the new tab and check
-    switch_tab(tabMessage);
-  }
-  assert_selected_and_displayed(curMessage);
-  close_tab(tabMessage);
-
-  assert_selected_and_displayed([3, 6]);
-}
-
-/**
- * Middle-click on the root of a collapsed thread, making sure that we don't
- * jump around in the thread tree.
- */
-function _middle_click_on_collapsed_thread_root_helper(aBackground) {
-  be_in_folder(threadedFolder);
-  make_display_threaded();
-  collapse_all_threads();
-
-  let folderTab = mc.tabmail.currentTabInfo;
-
-  let treeBox = mc.threadTree.treeBoxObject;
-  // Scroll to the top, then to the bottom
-  treeBox.ensureRowIsVisible(0);
-  treeBox.scrollByLines(mc.folderDisplay.view.dbView.rowCount);
-  // Note the first visible row
-  let preFirstRow = treeBox.getFirstVisibleRow();
-  // Middle-click on the root of the collapsed thread, which is also the last
-  // row
-  let [tabMessage, ] = middle_click_on_row(
-                           mc.folderDisplay.view.dbView.rowCount - 1);
-
-  if (!aBackground)
-    // Switch back to the folder tab
-    switch_tab(folderTab);
-
-  // Make sure the first visible row is still the same
-  if (treeBox.getFirstVisibleRow() != preFirstRow)
-    throw new Error("The first visible row should have been " + preFirstRow +
-        ", but is actually " + treeBox.getFirstVisibleRow() + ".");
-
-  close_tab(tabMessage);
-}
-
-/**
- * Middle-click on the root of an expanded thread, making sure that we don't
- * jump around in the thread tree.
- */
-function _middle_click_on_expanded_thread_root_helper(aBackground) {
-  be_in_folder(threadedFolder);
-  make_display_threaded();
-  expand_all_threads();
-
-  let folderTab = mc.tabmail.currentTabInfo;
-
-  let treeBox = mc.threadTree.treeBoxObject;
-  // Scroll to the top, then to near (but not exactly) the bottom
-  treeBox.ensureRowIsVisible(0);
-  treeBox.scrollToRow(mc.folderDisplay.view.dbView.rowCount -
-      treeBox.getPageLength() - (NUM_MESSAGES_IN_THREAD / 2));
-  // Note the first visible row
-  let preFirstRow = treeBox.getFirstVisibleRow();
-  // Middle-click on the root of the expanded thread, which is the row with
-  // index (number of rows - number of messages in thread).
-  let [tabMessage, ] = middle_click_on_row(
-      mc.folderDisplay.view.dbView.rowCount - NUM_MESSAGES_IN_THREAD);
-
-  if (!aBackground)
-    // Switch back to the folder tab
-    switch_tab(folderTab);
-
-  // Make sure the first visible row is still the same
-  if (treeBox.getFirstVisibleRow() != preFirstRow)
-    throw new Error("The first visible row should have been " + preFirstRow +
-        ", but is actually " + treeBox.getFirstVisibleRow() + ".");
-
-  close_tab(tabMessage);
-}
-
-/**
- * Generate background and foreground tests for each middle click test.
- *
- * @param aTests an array of test names
- */
-function _generate_background_foreground_tests(aTests) {
-  let self = this;
-  for each (let [, test] in Iterator(aTests)) {
-    let helperFunc = this["_" + test + "_helper"];
-    this["test_" + test + "_background"] = function() {
-      set_context_menu_background_tabs(true);
-      helperFunc.apply(self, [true]);
-      reset_context_menu_background_tabs();
-    };
-    this["test_" + test + "_foreground"] = function() {
-      set_context_menu_background_tabs(false);
-      helperFunc.apply(self, [false]);
-      reset_context_menu_background_tabs();
-    };
-  }
-}
-
-_generate_background_foreground_tests([
-  "middle_click_with_nothing_selected",
-  "middle_click_with_one_thing_selected",
-  "middle_click_with_many_things_selected",
-  "middle_click_on_existing_single_selection",
-  "middle_click_on_existing_multi_selection",
-  "middle_click_on_collapsed_thread_root",
-  "middle_click_on_expanded_thread_root"
-]);
-
-/**
- * Right-click on something and delete it, having no selection previously.
- */
-function test_right_click_deletion_nothing_selected() {
-  be_in_folder(folder);
-
-  select_none();
-  assert_selected_and_displayed();
-
-  let delMessage = right_click_on_row(3);
-  delete_via_popup();
-  // eh, might as well make sure the deletion worked while we are here
-  assert_message_not_in_view(delMessage);
-
-  assert_selected_and_displayed();
-}
-
-/**
- * We want to make sure that the selection post-delete still includes the same
- *  message (and that it is displayed).  In order for this to be interesting,
- *  we want to make sure that we right-click delete a message above the selected
- *  message so there is a shift in row numbering.
- */
-function test_right_click_deletion_one_other_thing_selected() {
-  be_in_folder(folder);
-
-  let curMessage = select_click_row(5);
-
-  let delMessage = right_click_on_row(3);
-  delete_via_popup();
-  assert_message_not_in_view(delMessage);
-
-  assert_selected_and_displayed(curMessage);
-}
-
-function test_right_click_deletion_many_other_things_selected() {
-  be_in_folder(folder);
-
-  select_click_row(4);
-  let messages = select_shift_click_row(6);
-
-  let delMessage = right_click_on_row(2);
-  delete_via_popup();
-  assert_message_not_in_view(delMessage);
-
-  assert_selected_and_displayed(messages);
-}
-
-function test_right_click_deletion_of_one_selected_thing() {
-  be_in_folder(folder);
-
-  let curMessage = select_click_row(2);
-
-  right_click_on_row(2);
-  delete_via_popup();
-  assert_message_not_in_view(curMessage);
-
-  if (!mc.folderDisplay.selectedCount)
-    throw new Error("We should have tried to select something!");
-}
-
-function test_right_click_deletion_of_many_selected_things() {
-  be_in_folder(folder);
-
-  select_click_row(2);
-  let messages = select_shift_click_row(4);
-
-  right_click_on_row(3);
-  delete_via_popup();
-  assert_messages_not_in_view(messages);
-
-  if (!mc.folderDisplay.selectedCount)
-    throw new Error("We should have tried to select something!");
-}
--- a/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
@@ -544,72 +544,161 @@ function select_shift_click_row(aViewInd
   if (hasMessageDisplay)
     wait_for_message_display_completion(aController);
   return aController.folderDisplay.selectedMessages;
 }
 
 /**
  * Helper function to click on a row with a given button.
  */
-function _row_click_helper(aViewIndex, aButton) {
-  let treeBox = mc.threadTree.treeBoxObject;
+function _row_click_helper(aTree, aViewIndex, aButton) {
+  let treeBox = aTree.treeBoxObject;
   // very important, gotta be able to see the row
   treeBox.ensureRowIsVisible(aViewIndex);
   // now figure out the coords
-  let children = mc.e("threadTree", {tagName: "treechildren"});
+  let children = mc.e(aTree.id, {tagName: "treechildren"});
   let x = children.boxObject.x;
   let y = children.boxObject.y;
   let rowX = 10;
   let rowY = treeBox.rowHeight * (aViewIndex - treeBox.getFirstVisibleRow());
   if (treeBox.getRowAt(x + rowX, y + rowY) != aViewIndex) {
     throw new Error("Thought we would find row " + aViewIndex + " at " +
                     rowX + "," + rowY + " but we found " +
                     treeBox.getRowAt(rowX, rowY));
   }
-  let tx = mc.threadTree.boxObject.x;
-  let ty = mc.threadTree.boxObject.y;
-  EventUtils.synthesizeMouse(mc.threadTree, x + rowX - tx, y + rowY - ty,
+  let tx = aTree.boxObject.x;
+  let ty = aTree.boxObject.y;
+  EventUtils.synthesizeMouse(aTree, x + rowX - tx, y + rowY - ty,
                              {type: "mousedown", button: aButton}, mc.window);
   if (aButton == 2)
-    EventUtils.synthesizeMouse(mc.threadTree, x + rowX - tx, y + rowY - ty,
+    EventUtils.synthesizeMouse(aTree, x + rowX - tx, y + rowY - ty,
                                {type: "contextmenu", button: aButton},
                                mc.window);
-  EventUtils.synthesizeMouse(mc.threadTree, x + rowX - tx, y + rowY - ty,
+  EventUtils.synthesizeMouse(aTree, x + rowX - tx, y + rowY - ty,
                              {type: "mouseup", button: aButton}, mc.window);
 }
 
 /**
  * Right-click on the tree-view in question.  With any luck, this will have
  *  the side-effect of opening up a pop-up which it is then on _your_ head
  *  to do something with or close.  However, we have helpful popup function
  *  helpers because I'm so nice.
  *
  * @return The message header that you clicked on.
  */
 function right_click_on_row(aViewIndex) {
   let msgHdr = mc.dbView.getMsgHdrAt(aViewIndex);
-  _row_click_helper(aViewIndex, 2);
+  _row_click_helper(mc.threadTree, aViewIndex, 2);
   return msgHdr;
 }
 
 /**
  * Middle-click on the tree-view in question, presumably opening a new message
  *  tab.
  *
  * @return [The new tab, the message that you clicked on.]
  */
 function middle_click_on_row(aViewIndex) {
   let msgHdr = mc.dbView.getMsgHdrAt(aViewIndex);
-  _row_click_helper(aViewIndex, 1);
+  _row_click_helper(mc.threadTree, aViewIndex, 1);
   // We append new tabs at the end, so return the last tab
   return [mc.tabmail.tabInfo[mc.tabmail.tabContainer.childNodes.length - 1],
           msgHdr];
 }
 
 /**
+ * Clear the selection in the folder tree view.
+ */
+function select_no_folders() {
+  wait_for_message_display_completion();
+  mc.folderTreeView.selection.clearSelection();
+  // give the event queue a chance to drain...
+  controller.sleep(0);
+}
+
+/**
+ * Pretend we are clicking on a folder with our mouse.
+ *
+ * @param aFolder The folder to click on. This needs to be present in the
+ *     current folder tree view, of course.
+ *
+ * @returns the view index that you clicked on.
+ */
+function select_click_folder(aFolder) {
+  wait_for_all_messages_to_load();
+
+  // this should set the current index as well as setting the selection.
+  let viewIndex = mc.folderTreeView.getIndexOfFolder(aFolder);
+  mc.folderTreeView.selection.select(viewIndex);
+  wait_for_all_messages_to_load();
+  // drain the event queue
+  controller.sleep(0);
+
+  return viewIndex;
+}
+
+/**
+ * Pretend we are clicking on a folder with our mouse with the shift key pressed.
+ *
+ * @param aFolder The folder to shift-click on. This needs to be present in the
+ *     current folder tree view, of course.
+ *
+ * @return An array containing all the folders that are now selected.
+ */
+function select_shift_click_folder(aFolder) {
+  wait_for_all_messages_to_load();
+
+  let viewIndex = mc.folderTreeView.getIndexOfFolder(aFolder);
+  // Passing -1 as the start range checks the shift-pivot, which should be -1,
+  //  so it should fall over to the current index, which is what we want.  It
+  //  will then set the shift-pivot to the previously-current-index and update
+  //  the current index to be what we shift-clicked on.  All matches user
+  //  interaction.
+  mc.folderTreeView.selection.rangedSelect(-1, viewIndex, false);
+  wait_for_all_messages_to_load();
+  // give the event queue a chance to drain...
+  controller.sleep(0);
+
+  return mc.folderTreeView.getSelectedFolders();
+}
+
+/**
+ * Right click on the folder tree view. With any luck, this will have the
+ * side-effect of opening up a pop-up which it is then on _your_ head to do
+ * something with or close.  However, we have helpful popup function helpers
+ * helpers because asuth's so nice.
+ *
+ * @note The argument is a folder here, unlike in the message case, so beware.
+ *
+ * @return The view index that you clicked on.
+ */
+function right_click_on_folder(aFolder) {
+  // Figure out the view index
+  let viewIndex = mc.folderTreeView.getIndexOfFolder(aFolder);
+  _row_click_helper(mc.folderTree, viewIndex, 2);
+  return viewIndex;
+}
+
+/**
+ * Middle-click on the folder tree view, presumably opening a new folder tab.
+ *
+ * @note The argument is a folder here, unlike in the message case, so beware.
+ *
+ * @return [The new tab, the view index that you clicked on.]
+ */
+function middle_click_on_folder(aFolder) {
+  // Figure out the view index
+  let viewIndex = mc.folderTreeView.getIndexOfFolder(aFolder);
+  _row_click_helper(mc.folderTree, viewIndex, 1);
+  // We append new tabs at the end, so return the last tab
+  return [mc.tabmail.tabInfo[mc.tabmail.tabContainer.childNodes.length - 1],
+          viewIndex];
+}
+
+/**
  * Assuming the context popup is popped-up (via right_click_on_row), select
  *  the deletion option.  If the popup is not popped up, you are out of luck.
  */
 function delete_via_popup() {
   plan_to_wait_for_folder_events("DeleteOrMoveMsgCompleted",
                                  "DeleteOrMoveMsgFailed");
   mc.click(mc.eid("mailContext-delete"));
   // for reasons unknown, the pop-up does not close itself?
@@ -1130,16 +1219,181 @@ function assert_visible(aViewIndexOrMess
   let treeBox = mc.threadTree.boxObject.QueryInterface(Ci.nsITreeBoxObject);
   if (viewIndex < treeBox.getFirstVisibleRow() ||
       viewIndex > treeBox.getLastVisibleRow())
     throw new Error("View index " + viewIndex + " is not visible! (" +
                     treeBox.getFirstVisibleRow() + "-" +
                     treeBox.getLastVisibleRow() + " are visible)");
 }
 
+function _normalize_folder_view_index(aViewIndex, aController) {
+  if (aController === undefined)
+    aController = mc;
+  if (aViewIndex < 0)
+    return aController.folderTreeView.QueryInterface(Ci.nsITreeView).rowCount +
+      aViewIndex;
+  return aViewIndex;
+}
+
+/**
+ * Helper function for use by assert_folders_selected /
+ * assert_folders_selected_and_displayed / assert_folder_displayed.
+ */
+function _process_row_folder_arguments() {
+  let troller = mc;
+  // - normalize into desired selected view indices
+  let desiredFolders = [];
+  for (let iArg = 0; iArg < arguments.length; iArg++) {
+    let arg = arguments[iArg];
+    // An integer identifying a view index
+    if (typeof(arg) == "number") {
+      let folder = troller.folderTreeView.getFolderForIndex(
+                       _normalize_folder_view_index(arg));
+      if (!folder)
+        throw new Error("Folder index not present in folder view: " + arg);
+      desiredFolders.push(folder);
+    }
+    // A folder
+    else if (arg instanceof Ci.nsIMsgFolder) {
+      desiredFolders.push(arg);
+    }
+    // A list containing two integers, indicating a range of view indices.
+    else if (arg.length == 2 && typeof(arg[0]) == "number") {
+      let lowIndex = _normalize_folder_view_index(arg[0]);
+      let highIndex = _normalize_folder_view_index(arg[1]);
+      for (let viewIndex = lowIndex; viewIndex <= highIndex; viewIndex++)
+        desiredFolders.push(troller.folderTreeView.getFolderForIndex(viewIndex));
+    }
+    // a List of folders
+    else if (arg.length !== undefined) {
+      for (let iFolder = 0; iFolder < arg.length; iFolder++) {
+        let folder = arg[iFolder].QueryInterface(Ci.nsIMsgFolder);
+        if (!folder)
+          throw new Error(arg[iFolder] + " is not a folder!");
+        desiredFolders.push(folder);
+      }
+    }
+    // it's a MozmillController
+    else if (arg.window) {
+      troller = arg;
+    }
+    else {
+      throw new Error("Illegal argument: " + arg);
+    }
+  }
+  // we can't really sort, so you'll have to grin and bear it
+  return [troller, desiredFolders];
+}
+
+/**
+ * Asserts that the given set of folders is selected.  Unless you are dealing
+ *  with transient selections resulting from right-clicks, you want to be using
+ *  assert_folders_selected_and_displayed because it makes sure that the
+ *  display is correct too.
+ *
+ * The arguments consist of one or more of the following:
+ * - A MozmillController, indicating we should use that controller instead of
+ *   the default, "mc" (corresponding to the 3pane.)  Pass this first!
+ * - An integer identifying a view index.
+ * - A list containing two integers, indicating a range of view indices.
+ * - An nsIMsgFolder.
+ * - A list of nsIMsgFolders.
+ */
+function assert_folders_selected() {
+  let [troller, desiredFolders] =
+    _process_row_folder_arguments.apply(this, arguments);
+
+  // - get the actual selection (already sorted by integer value)
+  let selectedFolders = troller.folderTreeView.getSelectedFolders();
+  // - test selection equivalence
+  // no shortcuts here. check if each folder in either array is present in the
+  // other array
+  if (desiredFolders.some(
+      function (folder) _non_strict_index_of(selectedFolders, folder) == -1) ||
+      selectedFolders.some(
+      function (folder) _non_strict_index_of(desiredFolders, folder) == -1))
+    throw new Error("Desired selection is: " +
+                    _prettify_folder_array(desiredFolders) + " but actual " +
+                    "selection is: " + _prettify_folder_array(selectedFolders));
+
+  return [troller, desiredFolders];
+}
+
+let assert_folder_selected = assert_folders_selected;
+
+/**
+ * Assert that the given folder is displayed, but not necessarily selected.
+ * Unless you are dealing with transient selection issues, you really should
+ * be using assert_folders_selected_and_displayed.
+ *
+ * The arguments consist of one or more of the following:
+ * - A MozmillController, indicating we should use that controller instead of
+ *   the default, "mc" (corresponding to the 3pane.)  Pass this first!
+ * - An integer identifying a view index.
+ * - A list containing two integers, indicating a range of view indices.
+ * - An nsIMsgFolder.
+ * - A list of nsIMsgFolders.
+ *
+ * In each case, since we can only have one folder displayed, we only look at
+ * the first folder you pass in.
+ */
+function assert_folder_displayed() {
+  let [troller, desiredFolders] =
+    _process_row_folder_arguments.apply(this, arguments);
+  if (troller.folderDisplay.displayedFolder != desiredFolders[0])
+    throw new Error("The displayed folder should be " +
+        desiredFolders[0].prettiestName + ", but is actually " +
+        troller.folderDisplay.displayedFolder.prettiestName);
+}
+
+/**
+ * Asserts that the folders corresponding to the one or more folder spec
+ * arguments are selected and displayed. If you specify multiple folders,
+ * we verify that all of them are selected and that the first folder you pass
+ * in is the one displayed. (If you don't pass in any folders, we can't assume
+ * anything, so we don't test that case.)
+ *
+ * The arguments consist of one or more of the following:
+ * - A MozmillController, indicating we should use that controller instead of
+ *   the default, "mc" (corresponding to the 3pane.)  Pass this first!
+ * - An integer identifying a view index.
+ * - A list containing two integers, indicating a range of view indices.
+ * - An nsIMsgFolder.
+ * - A list of nsIMsgFolders.
+ */
+function assert_folders_selected_and_displayed() {
+  let [troller, desiredFolders] = assert_folders_selected.apply(this,
+                                                                arguments);
+  if (desiredFolders.length > 0) {
+      if (troller.folderDisplay.displayedFolder != desiredFolders[0])
+        throw new Error("The displayed folder should be " +
+            desiredFolders[0].prettiestName + ", but is actually " +
+            troller.folderDisplay.displayedFolder.prettiestName);
+  }
+}
+
+let assert_no_folders_selected = assert_folders_selected_and_displayed;
+let assert_folder_selected_and_displayed =
+    assert_folders_selected_and_displayed;
+
+/**
+ * Since indexOf does strict equality checking, we need this.
+ */
+function _non_strict_index_of(aArray, aSearchElement) {
+  for ([i, item] in Iterator(aArray)) {
+    if (item == aSearchElement)
+      return i;
+  }
+  return -1;
+}
+
+function _prettify_folder_array(aArray) {
+  return aArray.map(function (folder) folder.prettiestName).join(", ");
+}
+
 /**
  * Put the view in unthreaded mode.
  */
 function make_display_unthreaded() {
   wait_for_message_display_completion();
   mc.folderDisplay.view.showUnthreaded = true;
   // drain event queue
   mc.sleep(0);
--- a/mail/test/mozmill/shared-modules/test-window-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-window-helpers.js
@@ -636,16 +636,17 @@ var PerWindowTypeAugmentations = {
    * The 3pane window is messenger.xul, the default window.
    */
   "mail:3pane": {
     /**
      * DOM elements to expose as attributes (by copying at augmentation time.)
      */
     elementsToExpose: {
       threadTree: "threadTree",
+      folderTree: "folderTree",
       tabmail: "tabmail",
     },
     /**
      * DOM elements to expose as elementslib.IDs as attributes (at augmentation
      *  time.)
      */
     elementIDsToExpose: {
       eThreadTree: "threadTree",