summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/folder-display
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/test/browser/folder-display')
-rw-r--r--comm/mail/test/browser/folder-display/browser.ini81
-rw-r--r--comm/mail/test/browser/folder-display/browser_applyView.js331
-rw-r--r--comm/mail/test/browser/folder-display/browser_archiveMessages.js132
-rw-r--r--comm/mail/test/browser/folder-display/browser_closeWindowOnDelete.js319
-rw-r--r--comm/mail/test/browser/folder-display/browser_columns.js955
-rw-r--r--comm/mail/test/browser/folder-display/browser_deletionFromVirtualFolders.js383
-rw-r--r--comm/mail/test/browser/folder-display/browser_deletionWithMultipleDisplays.js787
-rw-r--r--comm/mail/test/browser/folder-display/browser_displayName.js244
-rw-r--r--comm/mail/test/browser/folder-display/browser_folderPaneVisibility.js275
-rw-r--r--comm/mail/test/browser/folder-display/browser_folderToolbar.js147
-rw-r--r--comm/mail/test/browser/folder-display/browser_invalidDbFolderLoad.js61
-rw-r--r--comm/mail/test/browser/folder-display/browser_mailTelemetry.js135
-rw-r--r--comm/mail/test/browser/folder-display/browser_mailViews.js128
-rw-r--r--comm/mail/test/browser/folder-display/browser_messageCommands.js802
-rw-r--r--comm/mail/test/browser/folder-display/browser_messageCommandsOnMsgstore.js333
-rw-r--r--comm/mail/test/browser/folder-display/browser_messagePaneVisibility.js250
-rw-r--r--comm/mail/test/browser/folder-display/browser_messageReloads.js64
-rw-r--r--comm/mail/test/browser/folder-display/browser_messageSize.js80
-rw-r--r--comm/mail/test/browser/folder-display/browser_messageWindow.js153
-rw-r--r--comm/mail/test/browser/folder-display/browser_openingMessages.js186
-rw-r--r--comm/mail/test/browser/folder-display/browser_openingMessagesWithoutABackingView.js248
-rw-r--r--comm/mail/test/browser/folder-display/browser_readMsgs.js62
-rw-r--r--comm/mail/test/browser/folder-display/browser_recentMenu.js195
-rw-r--r--comm/mail/test/browser/folder-display/browser_rightClickMiddleClickFolders.js276
-rw-r--r--comm/mail/test/browser/folder-display/browser_rightClickMiddleClickMessages.js564
-rw-r--r--comm/mail/test/browser/folder-display/browser_savedsearchReloadAfterCompact.js105
-rw-r--r--comm/mail/test/browser/folder-display/browser_selection.js202
-rw-r--r--comm/mail/test/browser/folder-display/browser_summarization.js462
-rw-r--r--comm/mail/test/browser/folder-display/browser_syntheticViews.js292
-rw-r--r--comm/mail/test/browser/folder-display/browser_tabsSimple.js195
-rw-r--r--comm/mail/test/browser/folder-display/browser_viewSource.js222
-rw-r--r--comm/mail/test/browser/folder-display/browser_virtualFolderCommands.js83
-rw-r--r--comm/mail/test/browser/folder-display/browser_watchIgnoreThread.js150
-rw-r--r--comm/mail/test/browser/folder-display/data/test-invalid-vcard.eml25
-rw-r--r--comm/mail/test/browser/folder-display/head.js76
35 files changed, 9003 insertions, 0 deletions
diff --git a/comm/mail/test/browser/folder-display/browser.ini b/comm/mail/test/browser/folder-display/browser.ini
new file mode 100644
index 0000000000..591374afb5
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser.ini
@@ -0,0 +1,81 @@
+[DEFAULT]
+head = head.js
+prefs =
+ mail.account.account1.server=server1
+ mail.account.account2.identities=id1,id2
+ mail.account.account2.server=server2
+ mail.accountmanager.accounts=account1,account2
+ mail.accountmanager.defaultaccount=account2
+ mail.accountmanager.localfoldersserver=server1
+ mail.identity.id1.fullName=Tinderbox
+ mail.identity.id1.htmlSigFormat=false
+ mail.identity.id1.smtpServer=smtp1
+ mail.identity.id1.useremail=tinderbox@foo.invalid
+ mail.identity.id1.valid=true
+ mail.identity.id2.fullName=Tinderboxpushlog
+ mail.identity.id2.htmlSigFormat=true
+ mail.identity.id2.smtpServer=smtp1
+ mail.identity.id2.useremail=tinderboxpushlog@foo.invalid
+ mail.identity.id2.valid=true
+ mail.provider.suppress_dialog_on_startup=true
+ mail.server.server1.type=none
+ mail.server.server1.userName=nobody
+ mail.server.server2.check_new_mail=false
+ mail.server.server2.directory-rel=[ProfD]Mail/tinderbox
+ mail.server.server2.download_on_biff=true
+ mail.server.server2.hostname=tinderbox123
+ mail.server.server2.login_at_startup=false
+ mail.server.server2.name=tinderbox@foo.invalid
+ mail.server.server2.type=pop3
+ mail.server.server2.userName=tinderbox
+ mail.server.server2.whiteListAbURI=
+ mail.shell.checkDefaultClient=false
+ mail.smtp.defaultserver=smtp1
+ mail.smtpserver.smtp1.hostname=tinderbox123
+ mail.smtpserver.smtp1.username=tinderbox
+ mail.smtpservers=smtp1
+ mail.spotlight.firstRunDone=true
+ mail.startup.enabledMailCheckOnce=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+ ui.prefersReducedMotion=1
+subsuite = thunderbird
+support-files = data/**
+
+[browser_applyView.js]
+[browser_archiveMessages.js]
+[browser_closeWindowOnDelete.js]
+[browser_columns.js]
+[browser_deletionFromVirtualFolders.js]
+[browser_deletionWithMultipleDisplays.js]
+[browser_displayName.js]
+[browser_folderPaneVisibility.js]
+[browser_folderToolbar.js]
+skip-if = true # TODO
+[browser_invalidDbFolderLoad.js]
+[browser_mailTelemetry.js]
+[browser_mailViews.js]
+[browser_messageCommands.js]
+[browser_messageCommandsOnMsgstore.js]
+[browser_messagePaneVisibility.js]
+[browser_messageReloads.js]
+[browser_messageSize.js]
+[browser_messageWindow.js]
+[browser_openingMessages.js]
+[browser_openingMessagesWithoutABackingView.js]
+[browser_readMsgs.js]
+tags = vcard
+[browser_recentMenu.js]
+[browser_rightClickMiddleClickFolders.js]
+[browser_rightClickMiddleClickMessages.js]
+[browser_savedsearchReloadAfterCompact.js]
+[browser_selection.js]
+[browser_summarization.js]
+skip-if = true # TODO
+[browser_syntheticViews.js]
+skip-if = true # TODO
+[browser_tabsSimple.js]
+[browser_viewSource.js]
+[browser_virtualFolderCommands.js]
+[browser_watchIgnoreThread.js]
diff --git a/comm/mail/test/browser/folder-display/browser_applyView.js b/comm/mail/test/browser/folder-display/browser_applyView.js
new file mode 100644
index 0000000000..d5b5654fe4
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_applyView.js
@@ -0,0 +1,331 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests Apply Current View To…
+ */
+
+"use strict";
+
+var { be_in_folder, create_folder, get_about_3pane } = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { click_menus_in_sequence } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+// These are for the reset/apply to other/apply to other+child tests.
+var folderSource, folderParent, folderChild1;
+
+add_setup(async function () {
+ folderSource = await create_folder("ColumnsApplySource");
+
+ folderParent = await create_folder("ColumnsApplyParent");
+ folderParent.createSubfolder("Child1", null);
+ folderChild1 = folderParent.getChildNamed("Child1");
+ folderParent.createSubfolder("Child2", null);
+
+ await be_in_folder(folderSource);
+ await ensure_table_view();
+
+ registerCleanupFunction(async () => {
+ await ensure_cards_view();
+ folderParent.deleteSelf(null);
+ folderSource.deleteSelf(null);
+ });
+});
+
+/**
+ * Get the currently visible threadTree columns.
+ */
+add_task(async function testSetViewSingle() {
+ const info = folderSource.msgDatabase.dBFolderInfo;
+
+ Assert.equal(
+ info.viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "viewFlags should start threaded"
+ );
+ Assert.equal(
+ info.sortType,
+ Ci.nsMsgViewSortType.byDate,
+ "sortType should start byDate"
+ );
+ Assert.equal(
+ info.sortOrder,
+ Ci.nsMsgViewSortOrder.ascending,
+ "sortOrder should start ascending"
+ );
+
+ const about3Pane = get_about_3pane();
+
+ const threadCol = about3Pane.document.getElementById("threadCol");
+ EventUtils.synthesizeMouseAtCenter(threadCol, { clickCount: 1 }, about3Pane);
+ await TestUtils.waitForCondition(
+ () => info.viewFlags == Ci.nsMsgViewFlagsType.kNone,
+ "should change viewFlags to none"
+ );
+
+ const subjectCol = about3Pane.document.getElementById("subjectCol");
+ EventUtils.synthesizeMouseAtCenter(subjectCol, { clickCount: 1 }, about3Pane);
+ await TestUtils.waitForCondition(
+ () => info.sortType == Ci.nsMsgViewSortType.bySubject,
+ "should change sortType to subject"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(subjectCol, { clickCount: 1 }, about3Pane);
+ await TestUtils.waitForCondition(
+ () => info.sortOrder == Ci.nsMsgViewSortOrder.descending,
+ "should change sortOrder to sort descending"
+ );
+
+ Assert.equal(
+ info.viewFlags,
+ Ci.nsMsgViewFlagsType.kNone,
+ "viewFlags should now be unthreaded"
+ );
+ Assert.equal(
+ info.sortType,
+ Ci.nsMsgViewSortType.bySubject,
+ "sortType should now be bySubject"
+ );
+ Assert.equal(
+ info.sortOrder,
+ Ci.nsMsgViewSortOrder.descending,
+ "sortOrder should now be descending"
+ );
+});
+
+async function invoke_column_picker_option(aActions) {
+ const tabmail = document.getElementById("tabmail");
+ const about3Pane = tabmail.currentAbout3Pane;
+
+ const colPicker = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] button`
+ );
+ const colPickerPopup = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] menupopup`
+ );
+
+ const shownPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(colPicker, {}, about3Pane);
+ await shownPromise;
+ const hiddenPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popuphidden"
+ );
+ await click_menus_in_sequence(colPickerPopup, aActions);
+ await hiddenPromise;
+}
+
+async function _apply_to_folder_common(aChildrenToo, folder) {
+ let notificatonPromise;
+ if (aChildrenToo) {
+ notificatonPromise = TestUtils.topicObserved("msg-folder-views-propagated");
+ }
+
+ const menuItems = [
+ { class: "applyViewTo-menu" },
+ {
+ class: aChildrenToo
+ ? "applyViewToFolderAndChildren-menu"
+ : "applyViewToFolder-menu",
+ },
+ { label: "Local Folders" },
+ ];
+ if (!folder.isServer) {
+ menuItems.push({ label: folder.name });
+ }
+ menuItems.push(menuItems.at(-1));
+
+ const dialogPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ await invoke_column_picker_option(menuItems);
+ await dialogPromise;
+
+ if (notificatonPromise) {
+ await notificatonPromise;
+ }
+}
+
+/**
+ * Change settings in a folder, apply them to another folder that also has
+ * children. Make sure the folder changes but the children do not.
+ */
+add_task(async function test_apply_to_folder_no_children() {
+ const child1Info = folderChild1.msgDatabase.dBFolderInfo;
+ Assert.equal(
+ child1Info.viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "viewFlags for child1 should start threaded"
+ );
+ Assert.equal(
+ child1Info.sortType,
+ Ci.nsMsgViewSortType.byDate,
+ "sortType for child1 should start byDate"
+ );
+ Assert.equal(
+ child1Info.sortOrder,
+ Ci.nsMsgViewSortOrder.ascending,
+ "sortOrder for child1 should start ascending"
+ );
+
+ // Apply to the one dude
+ await _apply_to_folder_common(false, folderParent);
+
+ // Should apply to the folderParent.
+ Assert.equal(
+ folderParent.msgDatabase.dBFolderInfo.viewFlags,
+ Ci.nsMsgViewFlagsType.kNone,
+ "viewFlags should have been applied"
+ );
+ Assert.equal(
+ folderParent.msgDatabase.dBFolderInfo.sortType,
+ Ci.nsMsgViewSortType.bySubject,
+ "sortType should have been applied"
+ );
+ Assert.equal(
+ folderParent.msgDatabase.dBFolderInfo.sortOrder,
+ Ci.nsMsgViewSortOrder.descending,
+ "sortOrder should have been applied"
+ );
+
+ // Shouldn't have applied to its children.
+ Assert.equal(
+ folderChild1.msgDatabase.dBFolderInfo.viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "viewFlags should not have been applied to children"
+ );
+ Assert.equal(
+ folderChild1.msgDatabase.dBFolderInfo.sortType,
+ Ci.nsMsgViewSortType.byDate,
+ "sortType should not have been applied to children"
+ );
+ Assert.equal(
+ folderChild1.msgDatabase.dBFolderInfo.sortOrder,
+ Ci.nsMsgViewSortOrder.ascending,
+ "sortOrder should not have been applied to children"
+ );
+});
+
+/**
+ * Change settings in a folder, apply them to another folder and its children.
+ * Make sure the folder and its children change.
+ */
+add_task(async function test_apply_to_folder_and_children() {
+ await be_in_folder(folderSource);
+
+ const child1Info = folderChild1.msgDatabase.dBFolderInfo;
+ Assert.equal(
+ child1Info.viewFlags,
+ Ci.nsMsgViewFlagsType.kThreadedDisplay,
+ "viewFlags for child1 should start threaded"
+ );
+ Assert.equal(
+ child1Info.sortType,
+ Ci.nsMsgViewSortType.byDate,
+ "sortType for child1 should start byDate"
+ );
+ Assert.equal(
+ child1Info.sortOrder,
+ Ci.nsMsgViewSortOrder.ascending,
+ "sortOrder for child1 should start ascending"
+ );
+
+ // Apply to folder and children.
+ await _apply_to_folder_common(true, folderParent);
+
+ // Should apply to the folderParent.
+ Assert.equal(
+ folderParent.msgDatabase.dBFolderInfo.viewFlags,
+ Ci.nsMsgViewFlagsType.kNone,
+ "viewFlags should have been applied to parent"
+ );
+ Assert.equal(
+ folderParent.msgDatabase.dBFolderInfo.sortType,
+ Ci.nsMsgViewSortType.bySubject,
+ "sortType should have been applied to parent"
+ );
+ Assert.equal(
+ folderParent.msgDatabase.dBFolderInfo.sortOrder,
+ Ci.nsMsgViewSortOrder.descending,
+ "sortOrder should have been applied"
+ );
+
+ // Should have applied to its children as well.
+ for (const child of folderParent.descendants) {
+ Assert.equal(
+ child.msgDatabase.dBFolderInfo.viewFlags,
+ Ci.nsMsgViewFlagsType.kNone,
+ "viewFlags should have been applied to children"
+ );
+ Assert.equal(
+ child.msgDatabase.dBFolderInfo.sortType,
+ Ci.nsMsgViewSortType.bySubject,
+ "sortType should have been applied to children"
+ );
+ Assert.equal(
+ child.msgDatabase.dBFolderInfo.sortOrder,
+ Ci.nsMsgViewSortOrder.descending,
+ "sortOrder should have been applied to children"
+ );
+ }
+});
+
+/**
+ * Change settings in a folder, apply them to the root folder and its children.
+ * Make sure the children change.
+ */
+add_task(async function test_apply_to_root_folder_and_children() {
+ const info = folderSource.msgDatabase.dBFolderInfo;
+ await be_in_folder(folderSource);
+
+ const about3Pane = get_about_3pane();
+ const junkStatusCol = about3Pane.document.getElementById("junkStatusCol");
+ EventUtils.synthesizeMouseAtCenter(
+ junkStatusCol,
+ { clickCount: 1 },
+ about3Pane
+ );
+ Assert.equal(
+ info.viewFlags,
+ Ci.nsMsgViewFlagsType.kNone,
+ "viewFlags should be set to unthreaded"
+ );
+ Assert.equal(
+ info.sortType,
+ Ci.nsMsgViewSortType.byJunkStatus,
+ "sortType should be set to junkStatus"
+ );
+ Assert.equal(
+ info.sortOrder,
+ Ci.nsMsgViewSortOrder.ascending,
+ "sortOrder should be set to ascending"
+ );
+
+ // Apply to the root folder and its descendants.
+ await _apply_to_folder_common(true, folderSource.rootFolder);
+
+ // Make sure it is copied to all folders of this server.
+ for (const folder of folderSource.rootFolder.descendants) {
+ Assert.equal(
+ folder.msgDatabase.dBFolderInfo.viewFlags,
+ Ci.nsMsgViewFlagsType.kNone,
+ `viewFlags should have been applied to ${folder.name}`
+ );
+ Assert.equal(
+ folder.msgDatabase.dBFolderInfo.sortType,
+ Ci.nsMsgViewSortType.byJunkStatus,
+ `sortType should have been applied to ${folder.name}`
+ );
+ Assert.equal(
+ folder.msgDatabase.dBFolderInfo.sortOrder,
+ Ci.nsMsgViewSortOrder.ascending,
+ `sortOrder should have been applied to ${folder.name}`
+ );
+ folder.msgDatabase = null;
+ }
+});
diff --git a/comm/mail/test/browser/folder-display/browser_archiveMessages.js b/comm/mail/test/browser/folder-display/browser_archiveMessages.js
new file mode 100644
index 0000000000..7f3aabccc7
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_archiveMessages.js
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var {
+ add_message_sets_to_folders,
+ archive_messages,
+ assert_message_not_in_view,
+ assert_nothing_selected,
+ assert_selected,
+ assert_selected_and_displayed,
+ be_in_folder,
+ create_folder,
+ create_thread,
+ expand_all_threads,
+ get_about_3pane,
+ make_display_threaded,
+ mc,
+ select_click_row,
+ select_none,
+ select_shift_click_row,
+ toggle_thread_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder;
+
+/**
+ * The number of messages in the thread we use to test.
+ */
+var NUM_MESSAGES_IN_THREAD = 6;
+
+add_setup(async function () {
+ folder = await create_folder("ThreadedMessages");
+ let thread = create_thread(NUM_MESSAGES_IN_THREAD);
+ await add_message_sets_to_folders([folder], [thread]);
+ thread = create_thread(NUM_MESSAGES_IN_THREAD);
+ await add_message_sets_to_folders([folder], [thread]);
+});
+
+/**
+ * Test archiving messages that are not currently selected.
+ */
+add_task(async function test_batch_archiver() {
+ await be_in_folder(folder);
+
+ select_none();
+ assert_nothing_selected();
+
+ expand_all_threads();
+
+ /* Select the first (expanded) thread */
+ let root = select_click_row(0);
+ assert_selected_and_displayed(root);
+
+ /* Get a grip on the first and the second sub-message */
+ let m1 = select_click_row(1);
+ let m2 = select_click_row(2);
+ select_click_row(0);
+ assert_selected_and_displayed(root);
+
+ /* The root message is selected, we archive the first sub-message */
+ archive_messages([m1]);
+
+ /* This message is gone and the root message is still selected **/
+ assert_message_not_in_view([m1]);
+ assert_selected_and_displayed(root);
+
+ /* Now, archiving messages under a collapsed thread */
+ toggle_thread_row(0);
+ archive_messages([m2]);
+
+ /* Selection didn't change */
+ assert_selected(root);
+
+ /* And the message is gone */
+ toggle_thread_row(0);
+ assert_message_not_in_view([m2]);
+
+ /* Both threads are collapsed */
+ toggle_thread_row(0);
+
+ /* Get a grip on the second thread */
+ let root2 = select_click_row(1);
+ select_click_row(0);
+ assert_selected(root);
+
+ /* Archive the first thread, now the second thread should be selected */
+ Assert.ok(
+ Services.prefs.getBoolPref("mail.operate_on_msgs_in_collapsed_threads")
+ );
+ Assert.greater(get_about_3pane().gDBView.getSelectedMsgHdrs().length, 1);
+ archive_messages(get_about_3pane().gDBView.getSelectedMsgHdrs());
+ select_click_row(0); // TODO This should be unnecessary.
+ assert_selected(root2);
+
+ /* We only have the first thread left */
+ toggle_thread_row(0);
+ assert_selected_and_displayed(root2);
+ expand_all_threads();
+
+ /* Archive the head of the thread, check that it still works fine */
+ let child1 = select_click_row(1);
+ select_click_row(0);
+ archive_messages([root2]);
+ select_click_row(0); // TODO This should be unnecessary.
+ assert_selected_and_displayed(child1);
+
+ /* Test archiving a partial selection */
+ let child2 = select_click_row(1);
+ let child3 = select_click_row(2);
+ select_click_row(3);
+
+ select_shift_click_row(2);
+ select_shift_click_row(1);
+ select_shift_click_row(0);
+
+ archive_messages([child1, child3]);
+ assert_message_not_in_view([child1, child3]);
+ select_click_row(0); // TODO This should be unnecessary.
+ assert_selected_and_displayed(child2);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_closeWindowOnDelete.js b/comm/mail/test/browser/folder-display/browser_closeWindowOnDelete.js
new file mode 100644
index 0000000000..0927ab243a
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_closeWindowOnDelete.js
@@ -0,0 +1,319 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that the close message window on delete option works.
+ */
+
+"use strict";
+
+var {
+ assert_number_of_tabs_open,
+ be_in_folder,
+ close_tab,
+ create_folder,
+ make_message_sets_in_folders,
+ mc,
+ open_selected_message_in_new_tab,
+ open_selected_message_in_new_window,
+ press_delete,
+ reset_close_message_on_delete,
+ select_click_row,
+ set_close_message_on_delete,
+ switch_tab,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { close_window, plan_for_window_close, wait_for_window_close } =
+ ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+
+var folder;
+
+add_setup(async function () {
+ folder = await create_folder("CloseWindowOnDeleteA");
+ await make_message_sets_in_folders([folder], [{ count: 10 }]);
+});
+
+/**
+ * Delete a message and check that the message window is closed
+ * where appropriate.
+ */
+add_task(
+ async function test_close_message_window_on_delete_from_message_window() {
+ set_close_message_on_delete(true);
+ await be_in_folder(folder);
+
+ // select the first message
+ select_click_row(0);
+ // display it
+ let msgc = await open_selected_message_in_new_window();
+
+ select_click_row(1);
+ let msgc2 = await open_selected_message_in_new_window();
+
+ let preCount = folder.getTotalMessages(false);
+ msgc.window.focus();
+ plan_for_window_close(msgc);
+ press_delete(msgc);
+ if (folder.getTotalMessages(false) != preCount - 1) {
+ throw new Error("didn't delete a message before closing window");
+ }
+ wait_for_window_close(msgc);
+
+ if (msgc2.window.closed) {
+ throw new Error("should only have closed the active window");
+ }
+
+ close_window(msgc2);
+
+ reset_close_message_on_delete();
+ }
+);
+
+/**
+ * Delete a message when multiple windows are open to the message, and the
+ * message is deleted from one of them.
+ */
+add_task(
+ async function test_close_multiple_message_windows_on_delete_from_message_window() {
+ set_close_message_on_delete(true);
+ await be_in_folder(folder);
+
+ // select the first message
+ select_click_row(0);
+ // display it
+ let msgc = await open_selected_message_in_new_window();
+ let msgcA = await open_selected_message_in_new_window();
+
+ select_click_row(1);
+ let msgc2 = await open_selected_message_in_new_window();
+
+ let preCount = folder.getTotalMessages(false);
+ msgc.window.focus();
+ plan_for_window_close(msgc);
+ plan_for_window_close(msgcA);
+ press_delete(msgc);
+
+ if (folder.getTotalMessages(false) != preCount - 1) {
+ throw new Error("didn't delete a message before closing window");
+ }
+ wait_for_window_close(msgc);
+ wait_for_window_close(msgcA);
+
+ if (msgc2.window.closed) {
+ throw new Error("should only have closed the active window");
+ }
+
+ close_window(msgc2);
+
+ reset_close_message_on_delete();
+ }
+);
+
+/**
+ * Delete a message when multiple windows are open to the message, and the
+ * message is deleted from the 3-pane window.
+ */
+add_task(
+ async function test_close_multiple_message_windows_on_delete_from_3pane_window() {
+ set_close_message_on_delete(true);
+ await be_in_folder(folder);
+
+ // select the first message
+ select_click_row(0);
+ // display it
+ let msgc = await open_selected_message_in_new_window();
+ let msgcA = await open_selected_message_in_new_window();
+
+ select_click_row(1);
+ let msgc2 = await open_selected_message_in_new_window();
+
+ let preCount = folder.getTotalMessages(false);
+ mc.window.focus();
+ plan_for_window_close(msgc);
+ plan_for_window_close(msgcA);
+ select_click_row(0);
+ press_delete(mc);
+
+ if (folder.getTotalMessages(false) != preCount - 1) {
+ throw new Error("didn't delete a message before closing window");
+ }
+ wait_for_window_close(msgc);
+ wait_for_window_close(msgcA);
+
+ if (msgc2.window.closed) {
+ throw new Error("should only have closed the first window");
+ }
+
+ close_window(msgc2);
+
+ reset_close_message_on_delete();
+ }
+);
+
+/**
+ * Delete a message and check that the message tab is closed
+ * where appropriate.
+ */
+add_task(async function test_close_message_tab_on_delete_from_message_tab() {
+ set_close_message_on_delete(true);
+ await be_in_folder(folder);
+
+ // select the first message
+ select_click_row(0);
+ // display it
+ let msgc = await open_selected_message_in_new_tab(true);
+
+ select_click_row(1);
+ let msgc2 = await open_selected_message_in_new_tab(true);
+
+ let preCount = folder.getTotalMessages(false);
+ await switch_tab(msgc);
+ press_delete();
+
+ if (folder.getTotalMessages(false) != preCount - 1) {
+ throw new Error("didn't delete a message before closing tab");
+ }
+
+ assert_number_of_tabs_open(2);
+
+ if (msgc2 != mc.window.document.getElementById("tabmail").tabInfo[1]) {
+ throw new Error("should only have closed the active tab");
+ }
+
+ close_tab(msgc2);
+
+ reset_close_message_on_delete();
+});
+
+/**
+ * Delete a message when multiple windows are open to the message, and the
+ * message is deleted from one of them.
+ */
+add_task(
+ async function test_close_multiple_message_tabs_on_delete_from_message_tab() {
+ set_close_message_on_delete(true);
+ await be_in_folder(folder);
+
+ // select the first message
+ select_click_row(0);
+ // display it
+ let msgc = await open_selected_message_in_new_tab(true);
+ await open_selected_message_in_new_tab(true);
+
+ select_click_row(1);
+ let msgc2 = await open_selected_message_in_new_tab(true);
+
+ let preCount = folder.getTotalMessages(false);
+ await switch_tab(msgc);
+ press_delete();
+
+ if (folder.getTotalMessages(false) != preCount - 1) {
+ throw new Error("didn't delete a message before closing tab");
+ }
+
+ assert_number_of_tabs_open(2);
+
+ if (msgc2 != mc.window.document.getElementById("tabmail").tabInfo[1]) {
+ throw new Error("should only have closed the active tab");
+ }
+
+ close_tab(msgc2);
+
+ reset_close_message_on_delete();
+ }
+);
+
+/**
+ * Delete a message when multiple tabs are open to the message, and the
+ * message is deleted from the 3-pane window.
+ */
+add_task(
+ async function test_close_multiple_message_tabs_on_delete_from_3pane_window() {
+ set_close_message_on_delete(true);
+ await be_in_folder(folder);
+
+ // select the first message
+ select_click_row(0);
+ // display it
+ await open_selected_message_in_new_tab(true);
+ await open_selected_message_in_new_tab(true);
+
+ select_click_row(1);
+ let msgc2 = await open_selected_message_in_new_tab(true);
+
+ let preCount = folder.getTotalMessages(false);
+ mc.window.focus();
+ select_click_row(0);
+ press_delete(mc);
+
+ if (folder.getTotalMessages(false) != preCount - 1) {
+ throw new Error("didn't delete a message before closing window");
+ }
+
+ assert_number_of_tabs_open(2);
+
+ if (msgc2 != mc.window.document.getElementById("tabmail").tabInfo[1]) {
+ throw new Error("should only have closed the active tab");
+ }
+
+ close_tab(msgc2);
+
+ reset_close_message_on_delete();
+ }
+);
+
+/**
+ * Delete a message when multiple windows and tabs are open to the message, and
+ * the message is deleted from the 3-pane window.
+ */
+add_task(
+ async function test_close_multiple_windows_tabs_on_delete_from_3pane_window() {
+ set_close_message_on_delete(true);
+ await be_in_folder(folder);
+
+ // select the first message
+ select_click_row(0);
+ // display it
+ await open_selected_message_in_new_tab(true);
+ let msgcA = await open_selected_message_in_new_window();
+
+ select_click_row(1);
+ let msgc2 = await open_selected_message_in_new_tab(true);
+ let msgc2A = await open_selected_message_in_new_window();
+
+ let preCount = folder.getTotalMessages(false);
+ mc.window.focus();
+ plan_for_window_close(msgcA);
+ select_click_row(0);
+ press_delete(mc);
+
+ if (folder.getTotalMessages(false) != preCount - 1) {
+ throw new Error("didn't delete a message before closing window");
+ }
+ wait_for_window_close(msgcA);
+
+ assert_number_of_tabs_open(2);
+
+ if (msgc2 != mc.window.document.getElementById("tabmail").tabInfo[1]) {
+ throw new Error("should only have closed the active tab");
+ }
+
+ if (msgc2A.window.closed) {
+ throw new Error("should only have closed the first window");
+ }
+
+ close_tab(msgc2);
+ close_window(msgc2A);
+
+ reset_close_message_on_delete();
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+ }
+);
diff --git a/comm/mail/test/browser/folder-display/browser_columns.js b/comm/mail/test/browser/folder-display/browser_columns.js
new file mode 100644
index 0000000000..836f5c3fa4
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_columns.js
@@ -0,0 +1,955 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test column default logic and persistence logic. Persistence comes in both
+ * tab-switching (because of the multiplexed implementation) and
+ * folder-switching forms.
+ */
+
+"use strict";
+
+var {
+ be_in_folder,
+ close_tab,
+ create_folder,
+ create_virtual_folder,
+ enter_folder,
+ inboxFolder,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_tab,
+ switch_tab,
+ wait_for_all_messages_to_load,
+ select_click_row,
+ delete_messages,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { click_menus_in_sequence } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+// needed to zero inter-folder processing delay
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var { GlodaSyntheticView } = ChromeUtils.import(
+ "resource:///modules/gloda/GlodaSyntheticView.jsm"
+);
+
+var folderInbox, folderSent, folderVirtual, folderA, folderB;
+// INBOX_DEFAULTS sans 'dateCol' but gains 'tagsCol'
+var columnsB;
+
+// these are for the reset/apply to other/apply to other+child tests.
+var folderSource, folderParent, folderChild1, folderChild2;
+
+var gColumnStateUpdated = false;
+
+var useCorrespondent;
+var INBOX_DEFAULTS;
+var CARDS_INBOX_DEFAULT;
+var SENT_DEFAULTS;
+var CARDS_SENT_DEFAULTS;
+var VIRTUAL_DEFAULTS;
+var GLODA_DEFAULTS;
+
+add_setup(async function () {
+ useCorrespondent = Services.prefs.getBoolPref(
+ "mail.threadpane.use_correspondents"
+ );
+ INBOX_DEFAULTS = [
+ "threadCol",
+ "flaggedCol",
+ "attachmentCol",
+ "subjectCol",
+ "unreadButtonColHeader",
+ useCorrespondent ? "correspondentCol" : "senderCol",
+ "junkStatusCol",
+ "dateCol",
+ ];
+ CARDS_INBOX_DEFAULT = ["subjectCol", "senderCol", "dateCol", "tagsCol"];
+ SENT_DEFAULTS = [
+ "threadCol",
+ "flaggedCol",
+ "attachmentCol",
+ "subjectCol",
+ "unreadButtonColHeader",
+ useCorrespondent ? "correspondentCol" : "recipientCol",
+ "junkStatusCol",
+ "dateCol",
+ ];
+ CARDS_SENT_DEFAULTS = ["subjectCol", "recipientCol", "dateCol", "tagsCol"];
+ VIRTUAL_DEFAULTS = [
+ "threadCol",
+ "flaggedCol",
+ "attachmentCol",
+ "subjectCol",
+ "unreadButtonColHeader",
+ useCorrespondent ? "correspondentCol" : "senderCol",
+ "junkStatusCol",
+ "dateCol",
+ "locationCol",
+ ];
+ GLODA_DEFAULTS = [
+ "threadCol",
+ "flaggedCol",
+ "subjectCol",
+ useCorrespondent ? "correspondentCol" : "senderCol",
+ "dateCol",
+ "locationCol",
+ ];
+
+ // create the source
+ folderSource = await create_folder("ColumnsApplySource");
+
+ registerCleanupFunction(async () => {
+ await ensure_cards_view();
+ });
+});
+
+/**
+ * Get the currently visible threadTree columns.
+ *
+ * @returns {string[]}
+ */
+function get_visible_threadtree_columns() {
+ let tabmail = document.getElementById("tabmail");
+ let about3Pane = tabmail.currentAbout3Pane;
+
+ let columns = about3Pane.threadPane.columns;
+ return columns.filter(column => !column.hidden).map(column => column.id);
+}
+
+/**
+ * Verify that the provided list of columns is visible in the given order,
+ * throwing an exception if it is not the case.
+ *
+ * @param {string[]} desiredColumns - A list of column ID strings for columns
+ * that should be visible in the order that they should be visible.
+ */
+function assert_visible_columns(desiredColumns) {
+ let tabmail = document.getElementById("tabmail");
+ let about3Pane = tabmail.currentAbout3Pane;
+
+ let columns = about3Pane.threadPane.columns;
+ let visibleColumns = columns
+ .filter(column => !column.hidden)
+ .map(column => column.id);
+ let failCol = visibleColumns.filter(x => !desiredColumns.includes(x));
+ if (failCol.length) {
+ throw new Error(
+ `Found unexpected visible columns: '${failCol}'!\ndesired list: ${desiredColumns}\nactual list: ${visibleColumns}`
+ );
+ }
+}
+
+/**
+ * Verify that the provided list of columns is the expected list for the cards
+ * view.
+ *
+ * @param {string[]} desiredColumns - A list of column ID strings for columns
+ * that should be visible.
+ */
+function assert_visible_cards_columns(desiredColumns) {
+ const tabmail = document.getElementById("tabmail");
+ const about3Pane = tabmail.currentAbout3Pane;
+
+ const columns = about3Pane.threadPane.cardColumns;
+ const failCol = columns.filter(x => !desiredColumns.includes(x));
+ if (failCol.length) {
+ throw new Error(
+ `Found unexpected cards columns: '${failCol}'!\ndesired list: ${desiredColumns}\nactual list: ${columns}`
+ );
+ }
+}
+
+/**
+ * Toggle the column visibility .
+ *
+ * @param {string} columnID - Id of the thread column element to click.
+ */
+async function toggleColumn(columnID) {
+ let tabmail = document.getElementById("tabmail");
+ let about3Pane = tabmail.currentAbout3Pane;
+
+ let colPicker = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] button`
+ );
+ let colPickerPopup = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] menupopup`
+ );
+
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(colPicker, {}, about3Pane);
+ await shownPromise;
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popuphidden",
+ undefined,
+ event => event.originalTarget == colPickerPopup
+ );
+
+ const menuItem = colPickerPopup.querySelector(`[value="${columnID}"]`);
+ let checkedState = menuItem.getAttribute("checked");
+ let checkedStateChanged = TestUtils.waitForCondition(
+ () => checkedState != menuItem.getAttribute("checked"),
+ "The checked status changed"
+ );
+ colPickerPopup.activateItem(menuItem);
+ await checkedStateChanged;
+
+ // The column picker menupopup doesn't close automatically on purpose.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, about3Pane);
+ await hiddenPromise;
+}
+
+/**
+ * Make sure we set the proper defaults for an Inbox.
+ */
+add_task(async function test_column_defaults_inbox() {
+ // just use the inbox; comes from test-folder-display-helpers
+ folderInbox = inboxFolder;
+ await enter_folder(folderInbox);
+ await ensure_table_view();
+ assert_visible_columns(INBOX_DEFAULTS);
+ assert_visible_cards_columns(CARDS_INBOX_DEFAULT);
+});
+
+add_task(async function test_keypress_on_columns() {
+ const [messageSet] = await make_message_sets_in_folders(
+ [folderInbox],
+ [{ count: 1 }]
+ );
+ registerCleanupFunction(async () => {
+ await delete_messages(messageSet);
+ });
+
+ let tabmail = document.getElementById("tabmail");
+ let about3Pane = tabmail.currentAbout3Pane;
+
+ // Select the first row.
+ let row = about3Pane.threadTree.getRowAtIndex(0);
+ EventUtils.synthesizeMouseAtCenter(row, {}, about3Pane);
+
+ // Press SHIFT+TAB and LEFT to focus on the column picker.
+ EventUtils.synthesizeKey("VK_TAB", { shiftKey: true }, about3Pane);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, about3Pane);
+
+ Assert.equal(
+ about3Pane.document.activeElement,
+ about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] button`
+ ),
+ "The column picker should be focused"
+ );
+
+ Assert.equal(tabmail.tabInfo.length, 1, "Only 1 tab should be visible");
+
+ let colPickerPopup = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] menupopup`
+ );
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popupshown"
+ );
+ // Pressing Enter should open the column picker popup.
+ EventUtils.synthesizeKey("VK_RETURN", {}, about3Pane);
+ await shownPromise;
+
+ Assert.equal(
+ tabmail.tabInfo.length,
+ 1,
+ "The selected message shouldn't be opened in another tab"
+ );
+
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popuphidden",
+ undefined,
+ event => event.originalTarget == colPickerPopup
+ );
+ // Close the column picker.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, about3Pane);
+ await hiddenPromise;
+
+ // Move the focus to another column.
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {}, about3Pane);
+ Assert.notEqual(
+ about3Pane.document.activeElement,
+ about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] button`
+ ),
+ "The column picker should not be focused"
+ );
+
+ shownPromise = BrowserTestUtils.waitForEvent(colPickerPopup, "popupshown");
+ // Right clicking on a column header should trigger the column picker
+ // menupopup.
+ EventUtils.synthesizeMouseAtCenter(
+ about3Pane.document.activeElement,
+ { type: "contextmenu" },
+ about3Pane
+ );
+ await shownPromise;
+
+ hiddenPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popuphidden",
+ undefined,
+ event => event.originalTarget == colPickerPopup
+ );
+ // Close the column picker.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, about3Pane);
+ await hiddenPromise;
+});
+
+/**
+ * Make sure we set the proper defaults for a Sent folder.
+ */
+add_task(async function test_column_defaults_sent() {
+ folderSent = await create_folder("ColumnsSent");
+ folderSent.setFlag(Ci.nsMsgFolderFlags.SentMail);
+
+ await be_in_folder(folderSent);
+ assert_visible_columns(SENT_DEFAULTS);
+ assert_visible_cards_columns(CARDS_SENT_DEFAULTS);
+});
+
+/**
+ * Make sure we set the proper defaults for a multi-folder virtual folder.
+ */
+add_task(async function test_column_defaults_cross_folder_virtual_folder() {
+ folderVirtual = create_virtual_folder(
+ [folderInbox, folderSent],
+ {},
+ true,
+ "ColumnsVirtual"
+ );
+
+ await be_in_folder(folderVirtual);
+ assert_visible_columns(VIRTUAL_DEFAULTS);
+});
+
+/**
+ * Make sure that we initialize our columns from the inbox and that they persist
+ * after that and don't follow the inbox. This also does a good workout of the
+ * persistence logic.
+ */
+add_task(async function test_column_defaults_inherit_from_inbox() {
+ folderA = await create_folder("ColumnsA");
+ // - the folder should inherit from the inbox...
+ await be_in_folder(folderA);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ // - if we go back to the inbox and change things then the folder's settings
+ // should not change.
+ await be_in_folder(folderInbox);
+ // show tags, hide date
+ await toggleColumn("dateCol");
+ await toggleColumn("tagsCol");
+ // (paranoia verify)
+ columnsB = INBOX_DEFAULTS.slice(0, -1);
+ columnsB.push("tagsCol");
+ assert_visible_columns(columnsB);
+
+ // make sure A did not change; it should still have dateCol.
+ await be_in_folder(folderA);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ // and a newly created folder always gets the default set.
+ folderB = await create_folder("ColumnsB");
+ await be_in_folder(folderB);
+ assert_visible_columns(INBOX_DEFAULTS);
+ // Now change the columns for folder B so we can use it later.
+ await toggleColumn("dateCol");
+ await toggleColumn("tagsCol");
+
+ // - and if we restore the inbox, folder B should stay modified too.
+ await be_in_folder(folderInbox);
+ await toggleColumn("dateCol");
+ await toggleColumn("tagsCol");
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ await be_in_folder(folderB);
+ assert_visible_columns(columnsB);
+});
+
+/**
+ * Make sure that when we change tabs that things persist/restore correctly.
+ */
+add_task(async function test_column_visibility_persists_through_tab_changes() {
+ let tabA = await be_in_folder(folderA);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ let tabB = await open_folder_in_new_tab(folderB);
+ assert_visible_columns(columnsB);
+
+ // - switch back and forth among the loaded and verify
+ await switch_tab(tabA);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ await switch_tab(tabB);
+ assert_visible_columns(columnsB);
+
+ // - change things and make sure the changes stick
+ // B gain accountCol
+ let bWithExtra = columnsB.concat(["accountCol"]);
+ await toggleColumn("accountCol");
+ assert_visible_columns(bWithExtra);
+
+ await switch_tab(tabA);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ // A loses junk
+ let aSansJunk = INBOX_DEFAULTS.slice(0, -2); // nukes junk, date
+ await toggleColumn("junkStatusCol");
+ aSansJunk.push("dateCol"); // put date back
+ assert_visible_columns(aSansJunk);
+
+ await switch_tab(tabB);
+ assert_visible_columns(bWithExtra);
+ // B goes back to normal
+ await toggleColumn("accountCol");
+
+ await switch_tab(tabA);
+ assert_visible_columns(aSansJunk);
+ // A goes back to "normal"
+ await toggleColumn("junkStatusCol");
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ close_tab(tabB);
+});
+
+/**
+ * Make sure that when we change folders that things persist/restore correctly.
+ */
+add_task(
+ async function test_column_visibility_persists_through_folder_changes() {
+ await be_in_folder(folderA);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ // more for A
+ let aWithExtra = INBOX_DEFAULTS.concat(["sizeCol", "tagsCol"]);
+ await toggleColumn("sizeCol");
+ await toggleColumn("tagsCol");
+ assert_visible_columns(aWithExtra);
+
+ await be_in_folder(folderB);
+ assert_visible_columns(columnsB);
+
+ // B gain accountCol
+ let bWithExtra = columnsB.concat(["accountCol"]);
+ await toggleColumn("accountCol");
+ assert_visible_columns(bWithExtra);
+
+ // check A
+ await be_in_folder(folderA);
+ assert_visible_columns(aWithExtra);
+
+ // check B
+ await be_in_folder(folderB);
+ assert_visible_columns(bWithExtra);
+
+ // restore B
+ await toggleColumn("accountCol");
+
+ // restore A
+ await be_in_folder(folderA);
+ await toggleColumn("sizeCol");
+ await toggleColumn("tagsCol");
+
+ // check B
+ await be_in_folder(folderB);
+ assert_visible_columns(columnsB);
+
+ // check A
+ await be_in_folder(folderA);
+ assert_visible_columns(INBOX_DEFAULTS);
+ }
+);
+
+/**
+ * Test that reordering persists through tab changes and folder changes.
+ */
+add_task(async function test_column_reordering_persists() {
+ let tabA = await be_in_folder(folderA);
+ let tabB = await open_folder_in_new_tab(folderB);
+
+ let tabmail = document.getElementById("tabmail");
+ let about3Pane = tabmail.currentAbout3Pane;
+
+ // Move the tags column before the junk.
+ let tagsColButton = about3Pane.document.getElementById("tagsColButton");
+ tagsColButton.focus();
+ // Press Alt + Arrow Left twice to move the tags column before the junk
+ // status column.
+ EventUtils.synthesizeKey(
+ "KEY_ArrowLeft",
+ { altKey: true },
+ about3Pane.window
+ );
+ EventUtils.synthesizeKey(
+ "KEY_ArrowLeft",
+ { altKey: true },
+ about3Pane.window
+ );
+
+ // The columns in folderB should reflect the new order.
+ let reorderdB = columnsB.concat();
+ info(reorderdB);
+ reorderdB.splice(5, 0, reorderdB.splice(7, 1)[0]);
+ info(reorderdB);
+ assert_visible_columns(reorderdB);
+
+ // Move the tags column after the junk, the focus should still be on the
+ // tags button.
+ EventUtils.synthesizeKey(
+ "KEY_ArrowRight",
+ { altKey: true },
+ about3Pane.window
+ );
+
+ reorderdB.splice(6, 0, reorderdB.splice(5, 1)[0]);
+ assert_visible_columns(reorderdB);
+
+ await switch_tab(tabA);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ await switch_tab(tabB);
+ assert_visible_columns(reorderdB);
+
+ await be_in_folder(folderInbox);
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ await be_in_folder(folderB);
+ assert_visible_columns(reorderdB);
+
+ close_tab(tabB);
+});
+
+async function invoke_column_picker_option(aActions) {
+ let tabmail = document.getElementById("tabmail");
+ let about3Pane = tabmail.currentAbout3Pane;
+
+ let colPicker = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] button`
+ );
+ let colPickerPopup = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] menupopup`
+ );
+
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(colPicker, {}, about3Pane);
+ await shownPromise;
+ await click_menus_in_sequence(colPickerPopup, aActions);
+}
+
+/**
+ * The column picker's "reset columns to default" option should set our state
+ * back to the natural state.
+ */
+add_task(async function test_reset_to_inbox() {
+ // We should be in the inbox folder and have the default set unchanged.
+ assert_visible_columns(INBOX_DEFAULTS);
+
+ // Show the size column.
+ let conExtra = INBOX_DEFAULTS.concat(["sizeCol"]);
+ await toggleColumn("sizeCol");
+ assert_visible_columns(conExtra);
+
+ // Trigger a reset.
+ await invoke_column_picker_option([{ label: "Restore column order" }]);
+ // Ensure the default set was restored.
+ assert_visible_columns(INBOX_DEFAULTS);
+});
+
+async function _apply_to_folder_common(aChildrenToo, folder) {
+ let notificatonPromise;
+ if (aChildrenToo) {
+ notificatonPromise = TestUtils.topicObserved(
+ "msg-folder-columns-propagated"
+ );
+ }
+
+ const menuItems = [
+ { class: "applyTo-menu" },
+ {
+ class: aChildrenToo
+ ? "applyToFolderAndChildren-menu"
+ : "applyToFolder-menu",
+ },
+ { label: "Local Folders" },
+ ];
+ if (!folder.isServer) {
+ menuItems.push({ label: folder.name });
+ }
+ menuItems.push(menuItems.at(-1));
+
+ const dialogPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ await invoke_column_picker_option(menuItems);
+ await dialogPromise;
+
+ if (notificatonPromise) {
+ await notificatonPromise;
+ }
+}
+
+/**
+ * Change settings in a folder, apply them to another folder that also has
+ * children. Make sure the folder changes but the children do not.
+ */
+add_task(async function test_apply_to_folder_no_children() {
+ folderParent = await create_folder("ColumnsApplyParent");
+ folderParent.createSubfolder("Child1", null);
+ folderChild1 = folderParent.getChildNamed("Child1");
+ folderParent.createSubfolder("Child2", null);
+ folderChild2 = folderParent.getChildNamed("Child2");
+
+ await be_in_folder(folderSource);
+
+ // reset!
+ await invoke_column_picker_option([{ label: "Restore column order" }]);
+
+ // permute!
+ let conExtra = INBOX_DEFAULTS.concat(["sizeCol"]);
+ await toggleColumn("sizeCol");
+ assert_visible_columns(conExtra);
+
+ // apply to the one dude
+ await _apply_to_folder_common(false, folderParent);
+
+ // make sure it copied to the parent
+ await be_in_folder(folderParent);
+ assert_visible_columns(conExtra);
+
+ // but not the children
+ await be_in_folder(folderChild1);
+ assert_visible_columns(INBOX_DEFAULTS);
+ await be_in_folder(folderChild2);
+ assert_visible_columns(INBOX_DEFAULTS);
+});
+
+/**
+ * Change settings in a folder, apply them to another folder and its children.
+ * Make sure the folder and its children change.
+ */
+add_task(async function test_apply_to_folder_and_children() {
+ // no need to throttle ourselves during testing.
+ MailUtils.INTER_FOLDER_PROCESSING_DELAY_MS = 0;
+
+ await be_in_folder(folderSource);
+
+ // reset!
+ await invoke_column_picker_option([{ label: "Restore column order" }]);
+ let cols = get_visible_threadtree_columns();
+
+ // permute!
+ let conExtra = cols.concat(["tagsCol"]);
+ await toggleColumn("tagsCol");
+ assert_visible_columns(conExtra);
+
+ // apply to the dude and his offspring
+ await _apply_to_folder_common(true, folderParent);
+
+ // make sure it copied to the parent and his children
+ await be_in_folder(folderParent);
+ assert_visible_columns(conExtra);
+ await be_in_folder(folderChild1);
+ assert_visible_columns(conExtra);
+ await be_in_folder(folderChild2);
+ assert_visible_columns(conExtra);
+});
+
+/**
+ * Change settings in an incoming folder, apply them to an outgoing folder that
+ * also has children. Make sure the folder changes but the children do not.
+ */
+add_task(async function test_apply_to_folder_no_children_swapped() {
+ folderParent = await create_folder("ColumnsApplyParentOutgoing");
+ folderParent.setFlag(Ci.nsMsgFolderFlags.SentMail);
+ folderParent.createSubfolder("Child1", null);
+ folderChild1 = folderParent.getChildNamed("Child1");
+ folderParent.createSubfolder("Child2", null);
+ folderChild2 = folderParent.getChildNamed("Child2");
+
+ await be_in_folder(folderSource);
+
+ // reset!
+ await invoke_column_picker_option([{ label: "Restore column order" }]);
+
+ // permute!
+ let conExtra = [...INBOX_DEFAULTS];
+ if (useCorrespondent) {
+ conExtra[5] = "senderCol";
+ await toggleColumn("correspondentCol");
+ await toggleColumn("senderCol");
+ } else {
+ conExtra[5] = "correspondentCol";
+ await toggleColumn("senderCol");
+ await toggleColumn("correspondentCol");
+ }
+ assert_visible_columns(conExtra);
+
+ // Apply to the one dude.
+ await _apply_to_folder_common(false, folderParent);
+
+ // Make sure it copied to the parent.
+ let conExtraSwapped = [...SENT_DEFAULTS];
+ conExtraSwapped[5] = useCorrespondent ? "recipientCol" : "correspondentCol";
+ await be_in_folder(folderParent);
+ assert_visible_columns(conExtraSwapped);
+
+ // But not the children.
+ await be_in_folder(folderChild1);
+ assert_visible_columns(SENT_DEFAULTS);
+ await be_in_folder(folderChild2);
+ assert_visible_columns(SENT_DEFAULTS);
+});
+
+/**
+ * Change settings in an incoming folder, apply them to an outgoing folder and
+ * its children. Make sure the folder and its children change.
+ */
+add_task(async function test_apply_to_folder_and_children_swapped() {
+ // No need to throttle ourselves during testing.
+ MailUtils.INTER_FOLDER_PROCESSING_DELAY_MS = 0;
+
+ await be_in_folder(folderSource);
+
+ // reset order!
+ await invoke_column_picker_option([{ label: "Restore column order" }]);
+
+ // permute!
+ let conExtra = [...INBOX_DEFAULTS];
+ if (useCorrespondent) {
+ conExtra[5] = "senderCol";
+ await toggleColumn("correspondentCol");
+ await toggleColumn("senderCol");
+ } else {
+ conExtra[5] = "correspondentCol";
+ await toggleColumn("senderCol");
+ await toggleColumn("correspondentCol");
+ }
+ assert_visible_columns(conExtra);
+
+ // Apply to the dude and his offspring.
+ await _apply_to_folder_common(true, folderParent);
+
+ // Make sure it copied to the parent and his children.
+ let conExtraSwapped = [...SENT_DEFAULTS];
+ conExtraSwapped[5] = useCorrespondent ? "recipientCol" : "correspondentCol";
+ await be_in_folder(folderParent);
+ assert_visible_columns(conExtraSwapped);
+ await be_in_folder(folderChild1);
+ assert_visible_columns(conExtraSwapped);
+ await be_in_folder(folderChild2);
+ assert_visible_columns(conExtraSwapped);
+});
+
+/**
+ * Change settings in a folder, apply them to the root folder and its children.
+ * Make sure the children change.
+ */
+add_task(async function test_apply_to_root_folder_and_children() {
+ // No need to throttle ourselves during testing.
+ MailUtils.INTER_FOLDER_PROCESSING_DELAY_MS = 0;
+
+ await be_in_folder(folderSource);
+
+ // Reset!
+ await invoke_column_picker_option([{ label: "Restore column order" }]);
+ const cols = get_visible_threadtree_columns();
+
+ // Permute!
+ const conExtra = cols.concat(["locationCol"]);
+ await toggleColumn("locationCol");
+ assert_visible_columns(conExtra);
+
+ // Apply to the root folder and its descendants.
+ await _apply_to_folder_common(true, folderSource.rootFolder);
+
+ // Make sure it is copied to all folders of this server.
+ for (const folder of folderSource.rootFolder.descendants) {
+ await be_in_folder(folder);
+ assert_visible_columns(conExtra);
+ folder.msgDatabase = null;
+ }
+});
+
+/**
+ * Create a fake gloda collection.
+ */
+class FakeCollection {
+ constructor() {
+ this.items = [];
+ }
+}
+
+add_task(async function test_column_defaults_gloda_collection() {
+ let tabmail = document.getElementById("tabmail");
+ let tab = tabmail.openTab("mail3PaneTab", {
+ folderPaneVisible: false,
+ syntheticView: new GlodaSyntheticView({
+ collection: new FakeCollection(),
+ }),
+ title: "Test gloda results",
+ });
+ await BrowserTestUtils.waitForCondition(
+ () => tab.chromeBrowser.contentWindow.gViewWrapper?.isSynthetic,
+ "synthetic view loaded"
+ );
+ assert_visible_columns(GLODA_DEFAULTS);
+ close_tab(tab);
+});
+
+add_task(async function test_persist_columns_gloda_collection() {
+ let fakeCollection = new FakeCollection();
+ let tabmail = document.getElementById("tabmail");
+ let tab1 = tabmail.openTab("mail3PaneTab", {
+ folderPaneVisible: false,
+ syntheticView: new GlodaSyntheticView({
+ collection: fakeCollection,
+ }),
+ title: "Test gloda results 1",
+ });
+ await BrowserTestUtils.waitForCondition(
+ () => tab1.chromeBrowser.contentWindow.gViewWrapper?.isSynthetic,
+ "synthetic view loaded"
+ );
+
+ await toggleColumn("locationCol");
+ await toggleColumn("accountCol");
+
+ // GLODA_DEFAULTS sans 'locationCol' but gains 'accountCol'
+ let glodaColumns = GLODA_DEFAULTS.slice(0, -1);
+ glodaColumns.push("accountCol");
+
+ let tab2 = tabmail.openTab("mail3PaneTab", {
+ folderPaneVisible: false,
+ syntheticView: new GlodaSyntheticView({
+ collection: fakeCollection,
+ }),
+ title: "Test gloda results 2",
+ });
+ await BrowserTestUtils.waitForCondition(
+ () => tab2.chromeBrowser.contentWindow.gViewWrapper?.isSynthetic,
+ "synthetic view loaded"
+ );
+ assert_visible_columns(glodaColumns);
+
+ // Restore default gloda columns for debug ease.
+ await toggleColumn("locationCol");
+ await toggleColumn("accountCol");
+
+ close_tab(tab2);
+ close_tab(tab1);
+});
+
+add_task(async function test_reset_columns_gloda_collection() {
+ let fakeCollection = new FakeCollection();
+ let tabmail = document.getElementById("tabmail");
+ let tab1 = tabmail.openTab("mail3PaneTab", {
+ folderPaneVisible: false,
+ syntheticView: new GlodaSyntheticView({
+ collection: fakeCollection,
+ }),
+ title: "Test gloda results 1",
+ });
+ await BrowserTestUtils.waitForCondition(
+ () => tab1.chromeBrowser.contentWindow.gViewWrapper?.isSynthetic,
+ "synthetic view loaded"
+ );
+
+ await toggleColumn("locationCol");
+ await toggleColumn("accountCol");
+
+ // GLODA_DEFAULTS sans 'locationCol' but gains 'accountCol'
+ let glodaColumns = GLODA_DEFAULTS.slice(0, -1);
+ glodaColumns.push("accountCol");
+
+ assert_visible_columns(glodaColumns);
+
+ // reset order!
+ await invoke_column_picker_option([{ label: "Restore column order" }]);
+
+ assert_visible_columns(GLODA_DEFAULTS);
+
+ let tab2 = tabmail.openTab("mail3PaneTab", {
+ folderPaneVisible: false,
+ syntheticView: new GlodaSyntheticView({
+ collection: fakeCollection,
+ }),
+ title: "Test gloda results 2",
+ });
+ await BrowserTestUtils.waitForCondition(
+ () => tab2.chromeBrowser.contentWindow.gViewWrapper?.isSynthetic,
+ "synthetic view loaded"
+ );
+ assert_visible_columns(GLODA_DEFAULTS);
+
+ // Restore default gloda columns for debug ease.
+ await toggleColumn("locationCol");
+ await toggleColumn("accountCol");
+
+ close_tab(tab2);
+ close_tab(tab1);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
+
+add_task(async function test_double_click_column_picker() {
+ let doubleClickFolder = await create_folder("double click folder");
+ await make_message_sets_in_folders([doubleClickFolder], [{ count: 1 }]);
+ await be_in_folder(doubleClickFolder);
+ await select_click_row(0);
+
+ let tabmail = document.getElementById("tabmail");
+ const currentTabInfo = tabmail.currentTabInfo;
+ let about3Pane = tabmail.currentAbout3Pane;
+
+ let colPicker = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] button`
+ );
+ let colPickerPopup = about3Pane.document.querySelector(
+ `th[is="tree-view-table-column-picker"] menupopup`
+ );
+
+ let shownPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(colPicker, {}, about3Pane);
+ await shownPromise;
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ colPickerPopup,
+ "popuphidden",
+ undefined,
+ event => event.originalTarget == colPickerPopup
+ );
+
+ const menuItem = colPickerPopup.querySelector('[value="threadCol"]');
+ menuItem.dispatchEvent(new MouseEvent("dblclick", { button: 0 }));
+
+ // The column picker menupopup doesn't close automatically on purpose.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, about3Pane);
+ await hiddenPromise;
+
+ Assert.deepEqual(
+ tabmail.currentTabInfo,
+ currentTabInfo,
+ "No message was opened in a tab"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_deletionFromVirtualFolders.js b/comm/mail/test/browser/folder-display/browser_deletionFromVirtualFolders.js
new file mode 100644
index 0000000000..50566c62c0
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_deletionFromVirtualFolders.js
@@ -0,0 +1,383 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that deleting messages works from a virtual folder.
+ */
+
+"use strict";
+
+var {
+ assert_messages_in_view,
+ assert_selected_and_displayed,
+ assert_tab_titled_from,
+ be_in_folder,
+ create_folder,
+ get_smart_folder_named,
+ inboxFolder,
+ make_message_sets_in_folders,
+ mc,
+ open_selected_message_in_new_tab,
+ open_selected_message_in_new_window,
+ press_delete,
+ select_click_row,
+ switch_tab,
+ wait_for_all_messages_to_load,
+ get_about_3pane,
+ get_about_message,
+ delete_messages,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var {
+ plan_for_modal_dialog,
+ plan_for_window_close,
+ wait_for_modal_dialog,
+ wait_for_window_close,
+} = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+
+var { MailViewConstants } = ChromeUtils.import(
+ "resource:///modules/MailViewManager.jsm"
+);
+
+const { storeState } = ChromeUtils.importESModule(
+ "resource:///modules/CustomizationState.mjs"
+);
+
+var baseFolder, folder, lastMessageFolder;
+
+var tabFolder, tabMessage, tabMessageBackground, curMessage, nextMessage;
+
+var setNormal;
+
+/**
+ * The message window controller.
+ */
+var msgc;
+
+add_setup(async function () {
+ // Make sure the whole test runs with an unthreaded view in all folders.
+ Services.prefs.setIntPref("mailnews.default_view_flags", 0);
+
+ baseFolder = await create_folder("DeletionFromVirtualFoldersA");
+ // For setTagged, 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 display.
+ let [, setTagged] = await make_message_sets_in_folders(
+ [baseFolder],
+ [{ count: 4 }, { count: 4 }]
+ );
+ setTagged.addTag("$label1"); // Important, by default
+ // We depend on the count for this, too
+ [setNormal] = await make_message_sets_in_folders(
+ [inboxFolder],
+ [{ count: 4 }]
+ );
+
+ // Show the smart folders view.
+ get_about_3pane().folderPane.activeModes = ["all", "smart"];
+
+ // Add the view picker to the toolbar
+ storeState({
+ mail: ["view-picker"],
+ });
+ await BrowserTestUtils.waitForMutationCondition(
+ document.getElementById("unifiedToolbarContent"),
+ {
+ subtree: true,
+ childList: true,
+ },
+ () => document.querySelector("#unifiedToolbarContent .view-picker")
+ );
+
+ registerCleanupFunction(() => {
+ storeState({});
+ Services.prefs.clearUserPref("mailnews.default_view_flags");
+ get_about_3pane().folderPane.activeModes = ["all"];
+ });
+});
+
+// Check whether this message is displayed in the folder tab
+var VERIFY_FOLDER_TAB = 0x1;
+// Check whether this message is displayed in the foreground message tab
+var VERIFY_MESSAGE_TAB = 0x2;
+// Check whether this message is displayed in the background message tab
+var VERIFY_BACKGROUND_MESSAGE_TAB = 0x4;
+// Check whether this message is displayed in the message window
+var VERIFY_MESSAGE_WINDOW = 0x8;
+var VERIFY_ALL = 0xf;
+
+/**
+ * Verify that the message is displayed in the given tabs. The index is
+ * optional.
+ */
+async function _verify_message_is_displayed_in(aFlags, aMessage, aIndex) {
+ if (aFlags & VERIFY_FOLDER_TAB) {
+ await switch_tab(tabFolder);
+ assert_selected_and_displayed(aMessage);
+ if (aIndex !== undefined) {
+ assert_selected_and_displayed(aIndex);
+ }
+ }
+ if (aFlags & VERIFY_MESSAGE_TAB) {
+ // Verify the title first
+ assert_tab_titled_from(tabMessage, aMessage);
+ await switch_tab(tabMessage);
+ // Verify the title again, just in case
+ assert_tab_titled_from(tabMessage, aMessage);
+ assert_selected_and_displayed(aMessage);
+ if (aIndex !== undefined) {
+ assert_selected_and_displayed(aIndex);
+ }
+ }
+ if (aFlags & VERIFY_BACKGROUND_MESSAGE_TAB) {
+ // Only verify the title
+ assert_tab_titled_from(tabMessageBackground, aMessage);
+ }
+ if (aFlags & VERIFY_MESSAGE_WINDOW) {
+ assert_selected_and_displayed(msgc, aMessage);
+ if (aIndex !== undefined) {
+ assert_selected_and_displayed(msgc, aIndex);
+ }
+ }
+}
+
+add_task(async function test_create_virtual_folders() {
+ await be_in_folder(baseFolder);
+
+ // Apply the mail view
+ mc.window.RefreshAllViewPopups(
+ mc.window.document.getElementById("toolbarViewPickerPopup")
+ );
+ mc.window.ViewChange(":$label1");
+ wait_for_all_messages_to_load();
+
+ // - save it
+ plan_for_modal_dialog(
+ "mailnews:virtualFolderProperties",
+ subtest_save_mail_view
+ );
+ // we have to use value here because the option mechanism is not sophisticated
+ // enough.
+ mc.window.ViewChange(MailViewConstants.kViewItemVirtual);
+ wait_for_modal_dialog("mailnews:virtualFolderProperties");
+});
+
+function subtest_save_mail_view(savc) {
+ savc.window.document.querySelector("dialog").acceptDialog();
+}
+
+async function _open_first_message() {
+ // Enter the folder and open a message
+ tabFolder = await be_in_folder(folder);
+ curMessage = select_click_row(0);
+ assert_selected_and_displayed(curMessage);
+
+ // Open the tab with the message
+ tabMessage = await open_selected_message_in_new_tab();
+ assert_selected_and_displayed(curMessage);
+ assert_tab_titled_from(tabMessage, curMessage);
+
+ await switch_tab(tabFolder);
+
+ // Open another tab with the message, this time in the background
+ tabMessageBackground = await open_selected_message_in_new_tab(true);
+ assert_tab_titled_from(tabMessageBackground, curMessage);
+
+ // Open the window with the message
+ await switch_tab(tabFolder);
+ msgc = await open_selected_message_in_new_window();
+ assert_selected_and_displayed(msgc, curMessage);
+}
+
+add_task(async function test_open_first_message_in_virtual_folder() {
+ folder = baseFolder.getChildNamed(baseFolder.prettyName + "-Important");
+ if (!folder) {
+ throw new Error("DeletionFromVirtualFoldersA-Important was not created!");
+ }
+
+ await _open_first_message();
+});
+
+/**
+ * Perform a deletion from the folder tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_from_virtual_folder_in_folder_tab() {
+ const { gDBView } = get_about_3pane();
+ // - plan to end up on the guy who is currently at index 1
+ curMessage = gDBView.getMsgHdrAt(1);
+ // while we're at it, figure out who is at 2 for the next step
+ nextMessage = gDBView.getMsgHdrAt(2);
+ // - delete the message
+ press_delete();
+
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+});
+
+/**
+ * Perform a deletion from the message tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_from_virtual_folder_in_message_tab() {
+ await switch_tab(tabMessage);
+ // nextMessage is the guy we want to see once the delete completes.
+ press_delete();
+ curMessage = nextMessage;
+
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+
+ const { gDBView } = get_about_message();
+ // figure out the next guy...
+ nextMessage = gDBView.getMsgHdrAt(1);
+ if (!nextMessage) {
+ throw new Error("We ran out of messages early?");
+ }
+});
+
+/**
+ * Perform a deletion from the message window, verify the others update
+ * correctly (advancing to the next message).
+ */
+add_task(async function test_delete_from_virtual_folder_in_message_window() {
+ // - delete
+ press_delete(msgc);
+ curMessage = nextMessage;
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+});
+
+/**
+ * Delete the last message in that folder, which should close all message
+ * displays.
+ */
+add_task(
+ async function test_delete_last_message_from_virtual_folder_closes_message_displays() {
+ // - since we have both foreground and background message tabs, we don't need
+ // to open yet another tab to test
+
+ // - prep for the message window disappearing
+ plan_for_window_close(msgc);
+
+ // - let's arbitrarily perform the deletion on this message tab
+ await switch_tab(tabMessage);
+ press_delete();
+
+ // - the message window should have gone away...
+ // (this also helps ensure that the 3pane gets enough event loop time to do
+ // all that it needs to accomplish)
+ wait_for_window_close(msgc);
+ msgc = null;
+
+ // - and we should now be on the folder tab and there should be no other tabs
+ if (mc.window.document.getElementById("tabmail").tabInfo.length != 1) {
+ throw new Error("There should only be one tab left!");
+ }
+ // the below check is implied by the previous check if things are sane-ish
+ if (
+ mc.window.document.getElementById("tabmail").currentTabInfo != tabFolder
+ ) {
+ throw new Error("We should be on the folder tab!");
+ }
+ }
+);
+
+/**
+ * Open the first message in the smart inbox.
+ */
+add_task(async function test_open_first_message_in_smart_inbox() {
+ // Select the smart inbox
+ folder = get_smart_folder_named("Inbox");
+ await be_in_folder(folder);
+ assert_messages_in_view(setNormal);
+ // Open the first message
+ await _open_first_message();
+});
+
+/**
+ * Perform a deletion from the folder tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_from_smart_inbox_in_folder_tab() {
+ const { gDBView } = get_about_3pane();
+ // - plan to end up on the guy who is currently at index 1
+ curMessage = gDBView.getMsgHdrAt(1);
+ // while we're at it, figure out who is at 2 for the next step
+ nextMessage = gDBView.getMsgHdrAt(2);
+ // - delete the message
+ press_delete();
+
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+});
+
+/**
+ * Perform a deletion from the message tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_from_smart_inbox_in_message_tab() {
+ await switch_tab(tabMessage);
+ // nextMessage is the guy we want to see once the delete completes.
+ press_delete();
+ curMessage = nextMessage;
+
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+
+ const { gDBView } = get_about_message();
+ // figure out the next guy...
+ nextMessage = gDBView.getMsgHdrAt(1);
+ if (!nextMessage) {
+ throw new Error("We ran out of messages early?");
+ }
+});
+
+/**
+ * Perform a deletion from the message window, verify the others update
+ * correctly (advancing to the next message).
+ */
+add_task(async function test_delete_from_smart_inbox_in_message_window() {
+ // - delete
+ press_delete(msgc);
+ curMessage = nextMessage;
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+});
+
+/**
+ * Delete the last message in that folder, which should close all message
+ * displays.
+ */
+add_task(
+ async function test_delete_last_message_from_smart_inbox_closes_message_displays() {
+ // - since we have both foreground and background message tabs, we don't need
+ // to open yet another tab to test
+
+ // - prep for the message window disappearing
+ plan_for_window_close(msgc);
+
+ // - let's arbitrarily perform the deletion on this message tab
+ await switch_tab(tabMessage);
+ press_delete();
+
+ // - the message window should have gone away...
+ // (this also helps ensure that the 3pane gets enough event loop time to do
+ // all that it needs to accomplish)
+ wait_for_window_close(msgc);
+ msgc = null;
+
+ // - and we should now be on the folder tab and there should be no other tabs
+ if (mc.window.document.getElementById("tabmail").tabInfo.length != 1) {
+ throw new Error("There should only be one tab left!");
+ }
+ // the below check is implied by the previous check if things are sane-ish
+ if (
+ mc.window.document.getElementById("tabmail").currentTabInfo != tabFolder
+ ) {
+ throw new Error("We should be on the folder tab!");
+ }
+ }
+);
diff --git a/comm/mail/test/browser/folder-display/browser_deletionWithMultipleDisplays.js b/comm/mail/test/browser/folder-display/browser_deletionWithMultipleDisplays.js
new file mode 100644
index 0000000000..1bc4e67e49
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_deletionWithMultipleDisplays.js
@@ -0,0 +1,787 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that deleting a message in a given tab or window properly updates both
+ * that tab/window as well as all other tabs/windows. We also test that the
+ * message tab title updates appropriately through all of this. We do all of
+ * this both for tabs that have ever been opened in the foreground, and tabs
+ * that haven't (and thus might have fake selections).
+ */
+
+"use strict";
+
+var {
+ assert_selected_and_displayed,
+ assert_tab_titled_from,
+ be_in_folder,
+ close_message_window,
+ close_tab,
+ create_folder,
+ get_about_3pane,
+ get_about_message,
+ make_message_sets_in_folders,
+ mc,
+ open_selected_message_in_new_tab,
+ open_selected_message_in_new_window,
+ press_delete,
+ select_click_row,
+ select_control_click_row,
+ select_shift_click_row,
+ switch_tab,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { plan_for_window_close, wait_for_window_close } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+var folder,
+ lastMessageFolder,
+ oneBeforeFolder,
+ oneAfterFolder,
+ multipleDeletionFolder1,
+ multipleDeletionFolder2,
+ multipleDeletionFolder3,
+ multipleDeletionFolder4;
+
+// Adjust timeout to take care of code coverage runs needing twice as long.
+requestLongerTimeout(AppConstants.MOZ_CODE_COVERAGE ? 4 : 2);
+
+add_setup(async function () {
+ folder = await create_folder("DeletionA");
+ lastMessageFolder = await create_folder("DeletionB");
+ oneBeforeFolder = await create_folder("DeletionC");
+ oneAfterFolder = await create_folder("DeletionD");
+ multipleDeletionFolder1 = await create_folder("DeletionE");
+ multipleDeletionFolder2 = await create_folder("DeletionF");
+ multipleDeletionFolder3 = await create_folder("DeletionG");
+ multipleDeletionFolder4 = await create_folder("DeletionH");
+ // 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.
+ await make_message_sets_in_folders([folder], [{ count: 4 }]);
+
+ // since we don't test window close here, it doesn't really matter how many
+ // messages these have
+
+ await make_message_sets_in_folders([lastMessageFolder], [{ count: 4 }]);
+ await make_message_sets_in_folders([oneBeforeFolder], [{ count: 10 }]);
+ await make_message_sets_in_folders([oneAfterFolder], [{ count: 10 }]);
+ await make_message_sets_in_folders(
+ [multipleDeletionFolder1],
+ [{ count: 30 }]
+ );
+
+ // We're depending on selecting the last message here, so these do matter
+ await make_message_sets_in_folders(
+ [multipleDeletionFolder2],
+ [{ count: 10 }]
+ );
+ await make_message_sets_in_folders(
+ [multipleDeletionFolder3],
+ [{ count: 10 }]
+ );
+ await make_message_sets_in_folders(
+ [multipleDeletionFolder4],
+ [{ count: 10 }]
+ );
+});
+
+var tabFolder, tabMessage, tabMessageBackground, curMessage, nextMessage;
+
+/**
+ * The message window controller. Short names because controllers get used a
+ * lot.
+ */
+var msgc;
+
+/**
+ * Open up the message at aIndex in all our display mechanisms, and check to see
+ * if the displays are all correct. This also sets up all our globals.
+ */
+async function _open_message_in_all_four_display_mechanisms_helper(
+ aFolder,
+ aIndex
+) {
+ // - Select the message in this tab.
+ tabFolder = await be_in_folder(aFolder);
+ curMessage = select_click_row(aIndex);
+ assert_selected_and_displayed(curMessage);
+
+ // - Open the tab with the message
+ tabMessage = await open_selected_message_in_new_tab();
+ assert_selected_and_displayed(curMessage);
+ assert_tab_titled_from(tabMessage, curMessage);
+
+ // go back to the folder tab
+ await switch_tab(tabFolder);
+
+ // - Open another tab with the message, this time in the background
+ tabMessageBackground = await open_selected_message_in_new_tab(true);
+ assert_tab_titled_from(tabMessageBackground, curMessage);
+
+ // - Open the window with the message
+ // need to go back to the folder tab. (well, should.)
+ await switch_tab(tabFolder);
+ msgc = await open_selected_message_in_new_window();
+ assert_selected_and_displayed(msgc, curMessage);
+}
+
+// Check whether this message is displayed in the folder tab
+var VERIFY_FOLDER_TAB = 0x1;
+// Check whether this message is displayed in the foreground message tab
+var VERIFY_MESSAGE_TAB = 0x2;
+// Check whether this message is displayed in the background message tab
+var VERIFY_BACKGROUND_MESSAGE_TAB = 0x4;
+// Check whether this message is displayed in the message window
+var VERIFY_MESSAGE_WINDOW = 0x8;
+var VERIFY_ALL = 0xf;
+
+/**
+ * Verify that the message is displayed in the given tabs. The index is
+ * optional.
+ */
+async function _verify_message_is_displayed_in(aFlags, aMessage, aIndex) {
+ if (aFlags & VERIFY_FOLDER_TAB) {
+ await switch_tab(tabFolder);
+ Assert.equal(
+ get_about_message().gMessage,
+ aMessage,
+ "folder tab shows the correct message"
+ );
+ assert_selected_and_displayed(aMessage);
+ if (aIndex !== undefined) {
+ assert_selected_and_displayed(aIndex);
+ }
+ }
+ if (aFlags & VERIFY_MESSAGE_TAB) {
+ // Verify the title first
+ assert_tab_titled_from(tabMessage, aMessage);
+ await switch_tab(tabMessage);
+ // Verify the title again, just in case
+ Assert.equal(
+ get_about_message().gMessageURI,
+ aMessage.folder.getUriForMsg(aMessage)
+ );
+ assert_tab_titled_from(tabMessage, aMessage);
+ Assert.equal(
+ get_about_message().gMessage,
+ aMessage,
+ "message tab shows the correct message"
+ );
+ assert_selected_and_displayed(aMessage);
+ if (aIndex !== undefined) {
+ assert_selected_and_displayed(aIndex);
+ }
+ }
+ if (aFlags & VERIFY_BACKGROUND_MESSAGE_TAB) {
+ // Only verify the title
+ assert_tab_titled_from(tabMessageBackground, aMessage);
+ }
+ if (aFlags & VERIFY_MESSAGE_WINDOW) {
+ Assert.equal(
+ get_about_message(msgc.window).gMessage,
+ aMessage,
+ "message window shows the correct message"
+ );
+ assert_selected_and_displayed(msgc, aMessage);
+ if (aIndex !== undefined) {
+ assert_selected_and_displayed(msgc, aIndex);
+ }
+ }
+}
+
+/**
+ * Have a message displayed in a folder tab, message tab (foreground and
+ * background), and message window. The idea is that as we delete from the
+ * various sources, they should all advance in lock-step through their messages,
+ * simplifying our lives (but making us explode forevermore the first time any
+ * of the tests fail.)
+ */
+add_task(
+ async function test_open_first_message_in_all_four_display_mechanisms() {
+ await _open_message_in_all_four_display_mechanisms_helper(folder, 0);
+ }
+);
+
+/**
+ * Perform a deletion from the folder tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_in_folder_tab() {
+ let about3Pane = get_about_3pane();
+ // - plan to end up on the guy who is currently at index 1
+ curMessage = about3Pane.gDBView.getMsgHdrAt(1);
+ // while we're at it, figure out who is at 2 for the next step
+ nextMessage = about3Pane.gDBView.getMsgHdrAt(2);
+ // - delete the message
+ press_delete();
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+});
+
+/**
+ * Perform a deletion from the message tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_in_message_tab() {
+ await switch_tab(tabMessage);
+ // nextMessage is the guy we want to see once the delete completes.
+ press_delete();
+ curMessage = nextMessage;
+
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+
+ // figure out the next guy...
+ nextMessage = get_about_message().gDBView.getMsgHdrAt(1);
+ if (!nextMessage) {
+ throw new Error("We ran out of messages early?");
+ }
+});
+
+/**
+ * Perform a deletion from the message window, verify the others update
+ * correctly (advancing to the next message).
+ */
+add_task(async function test_delete_in_message_window() {
+ // - delete
+ press_delete(msgc);
+ curMessage = nextMessage;
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+});
+
+/**
+ * Delete the last message in that folder, which should close all message
+ * displays.
+ */
+add_task(async function test_delete_last_message_closes_message_displays() {
+ // - since we have both foreground and background message tabs, we don't need
+ // to open yet another tab to test
+
+ // - prep for the message window disappearing
+ plan_for_window_close(msgc);
+
+ // - let's arbitrarily perform the deletion on this message tab
+ await switch_tab(tabMessage);
+ press_delete();
+
+ // - the message window should have gone away...
+ // (this also helps ensure that the 3pane gets enough event loop time to do
+ // all that it needs to accomplish)
+ wait_for_window_close(msgc);
+ msgc = null;
+
+ // - and we should now be on the folder tab and there should be no other tabs
+ if (mc.window.document.getElementById("tabmail").tabInfo.length != 1) {
+ throw new Error("There should only be one tab left!");
+ }
+ // the below check is implied by the previous check if things are sane-ish
+ if (
+ mc.window.document.getElementById("tabmail").currentTabInfo != tabFolder
+ ) {
+ throw new Error("We should be on the folder tab!");
+ }
+});
+
+/*
+ * Now we retest everything, but while deleting the last message in our
+ * selection. We need to make sure we select the previously next-to-last message
+ * in that case.
+ */
+
+/**
+ * Have the last message displayed in a folder tab, message tab (foreground and
+ * background), and message window. The idea is that as we delete from the
+ * various sources, they should all advance in lock-step through their messages,
+ * simplifying our lives (but making us explode forevermore the first time any
+ * of the tests fail.)
+ */
+add_task(
+ async function test_open_last_message_in_all_four_display_mechanisms() {
+ // since we have four messages, index 3 is the last message.
+ await _open_message_in_all_four_display_mechanisms_helper(
+ lastMessageFolder,
+ 3
+ );
+ }
+);
+
+/**
+ * Perform a deletion from the folder tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_last_message_in_folder_tab() {
+ let about3Pane = get_about_3pane();
+ // - plan to end up on the guy who is currently at index 2
+ curMessage = about3Pane.gDBView.getMsgHdrAt(2);
+ // while we're at it, figure out who is at 1 for the next step
+ nextMessage = about3Pane.gDBView.getMsgHdrAt(1);
+ // - delete the message
+ press_delete();
+
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 2);
+});
+
+/**
+ * Perform a deletion from the message tab, verify the others update correctly
+ * (advancing to the next message).
+ */
+add_task(async function test_delete_last_message_in_message_tab() {
+ // (we're still on the message tab, and nextMessage is the guy we want to see
+ // once the delete completes.)
+ press_delete();
+ curMessage = nextMessage;
+
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 1);
+ // figure out the next guy...
+
+ nextMessage = get_about_message().gDBView.getMsgHdrAt(0);
+ if (!nextMessage) {
+ throw new Error("We ran out of messages early?");
+ }
+});
+
+/**
+ * Perform a deletion from the message window, verify the others update
+ * correctly (advancing to the next message).
+ */
+add_task(async function test_delete_last_message_in_message_window() {
+ // Vary this up. Switch to the folder tab instead of staying on the message
+ // tab
+ await switch_tab(tabFolder);
+ // - delete
+ press_delete(msgc);
+ curMessage = nextMessage;
+ // - verify all displays
+ await _verify_message_is_displayed_in(VERIFY_ALL, curMessage, 0);
+
+ // - clean up, close the message window and displays
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+});
+
+/*
+ * Our next job is to open up a message, then delete the message one before it
+ * in another view. The other selections shouldn't be affected.
+ */
+
+/**
+ * Test "one before" deletion in the folder tab.
+ */
+add_task(async function test_delete_one_before_message_in_folder_tab() {
+ // Open up message 4 in message tabs and a window (we'll delete message 3).
+ await _open_message_in_all_four_display_mechanisms_helper(oneBeforeFolder, 4);
+
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(4);
+ select_click_row(3);
+ press_delete();
+
+ // The message tab, background message tab and window shouldn't have changed
+ await _verify_message_is_displayed_in(
+ VERIFY_MESSAGE_TAB | VERIFY_BACKGROUND_MESSAGE_TAB | VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+});
+
+/**
+ * Test "one before" deletion in the message tab.
+ */
+add_task(async function test_delete_one_before_message_in_message_tab() {
+ // Open up 3 in a message tab, then select and open up 4 in a background tab
+ // and window.
+ select_click_row(3);
+ tabMessage = await open_selected_message_in_new_tab(true);
+ let expectedMessage = select_click_row(4);
+ tabMessageBackground = await open_selected_message_in_new_tab(true);
+ msgc = await open_selected_message_in_new_window(true);
+
+ // Switch to the message tab, and delete.
+ await switch_tab(tabMessage);
+ press_delete();
+
+ // The folder tab, background message tab and window shouldn't have changed
+ await _verify_message_is_displayed_in(
+ VERIFY_FOLDER_TAB | VERIFY_BACKGROUND_MESSAGE_TAB | VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+});
+
+/**
+ * Test "one before" deletion in the message window.
+ */
+add_task(async function test_delete_one_before_message_in_message_window() {
+ // Open up 3 in a message window, then select and open up 4 in a background
+ // and a foreground tab.
+ select_click_row(3);
+ msgc = await open_selected_message_in_new_window();
+ let expectedMessage = select_click_row(4);
+ tabMessage = await open_selected_message_in_new_tab();
+ await switch_tab(tabFolder);
+ tabMessageBackground = await open_selected_message_in_new_tab(true);
+
+ // Press delete in the message window.
+ press_delete(msgc);
+
+ // The folder tab, message tab and background message tab shouldn't have
+ // changed
+ await _verify_message_is_displayed_in(
+ VERIFY_FOLDER_TAB | VERIFY_MESSAGE_TAB | VERIFY_BACKGROUND_MESSAGE_TAB,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+});
+
+/*
+ * Now do all of that again, but this time delete the message _after_ the open one.
+ */
+
+/**
+ * Test "one after" deletion in the folder tab.
+ */
+add_task(async function test_delete_one_after_message_in_folder_tab() {
+ // Open up message 4 in message tabs and a window (we'll delete message 5).
+ await _open_message_in_all_four_display_mechanisms_helper(oneAfterFolder, 4);
+
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(4);
+ select_click_row(5);
+ press_delete();
+
+ // The message tab, background message tab and window shouldn't have changed
+ await _verify_message_is_displayed_in(
+ VERIFY_MESSAGE_TAB | VERIFY_BACKGROUND_MESSAGE_TAB | VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+});
+
+/**
+ * Test "one after" deletion in the message tab.
+ */
+add_task(async function test_delete_one_after_message_in_message_tab() {
+ // Open up 5 in a message tab, then select and open up 4 in a background tab
+ // and window.
+ select_click_row(5);
+ tabMessage = await open_selected_message_in_new_tab(true);
+ let expectedMessage = select_click_row(4);
+ tabMessageBackground = await open_selected_message_in_new_tab(true);
+ msgc = await open_selected_message_in_new_window(true);
+
+ // Switch to the message tab, and delete.
+ await switch_tab(tabMessage);
+ press_delete();
+
+ // The folder tab, background message tab and window shouldn't have changed
+ await _verify_message_is_displayed_in(
+ VERIFY_FOLDER_TAB | VERIFY_BACKGROUND_MESSAGE_TAB | VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+});
+
+/**
+ * Test "one after" deletion in the message window.
+ */
+add_task(async function test_delete_one_after_message_in_message_window() {
+ // Open up 5 in a message window, then select and open up 4 in a background
+ // and a foreground tab.
+ select_click_row(5);
+ msgc = await open_selected_message_in_new_window();
+ let expectedMessage = select_click_row(4);
+ tabMessage = await open_selected_message_in_new_tab();
+ await switch_tab(tabFolder);
+ tabMessageBackground = await open_selected_message_in_new_tab(true);
+
+ // Press delete in the message window.
+ press_delete(msgc);
+
+ // The folder tab, message tab and background message tab shouldn't have
+ // changed
+ await _verify_message_is_displayed_in(
+ VERIFY_FOLDER_TAB | VERIFY_MESSAGE_TAB | VERIFY_BACKGROUND_MESSAGE_TAB,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+});
+
+/*
+ * Delete multiple messages in a folder tab. Make sure message displays at the
+ * beginning, middle and end of a selection work out.
+ */
+
+/**
+ * Test deleting multiple messages in a folder tab, with message displays open
+ * to the beginning of a selection.
+ */
+add_task(
+ async function test_delete_multiple_messages_with_first_selected_message_open() {
+ // Open up 2 in a message tab, background tab, and message window.
+ await _open_message_in_all_four_display_mechanisms_helper(
+ multipleDeletionFolder1,
+ 2
+ );
+
+ // We'll select 2-5, 8, 9 and 10. We expect 6 to be the next displayed
+ // message.
+ select_click_row(2);
+ select_shift_click_row(5);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ select_control_click_row(10);
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(6);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ await _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+ }
+);
+
+/**
+ * Test deleting multiple messages in a folder tab, with message displays open
+ * to somewhere in the middle of a selection.
+ */
+add_task(
+ async function test_delete_multiple_messages_with_nth_selected_message_open() {
+ // Open up 9 in a message tab, background tab, and message window.
+ await _open_message_in_all_four_display_mechanisms_helper(
+ multipleDeletionFolder1,
+ 9
+ );
+
+ // We'll select 2-5, 8, 9 and 10. We expect 11 to be the next displayed
+ // message.
+ select_click_row(2);
+ select_shift_click_row(5);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ select_control_click_row(10);
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(11);
+
+ // Delete the selected messages
+ press_delete();
+
+ // The folder tab should now be showing message 2
+ assert_selected_and_displayed(2);
+
+ // The other displays should now be showing the expectedMessage
+ await _verify_message_is_displayed_in(
+ VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+ }
+);
+
+/**
+ * Test deleting multiple messages in a folder tab, with message displays open
+ * to the end of a selection.
+ */
+add_task(
+ async function test_delete_multiple_messages_with_last_selected_message_open() {
+ // Open up 10 in a message tab, background tab, and message window.
+ await _open_message_in_all_four_display_mechanisms_helper(
+ multipleDeletionFolder1,
+ 9
+ );
+
+ // We'll select 2-5, 8, 9 and 10. We expect 11 to be the next displayed
+ // message.
+ select_click_row(2);
+ select_shift_click_row(5);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ select_control_click_row(10);
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(11);
+
+ // Delete the selected messages
+ press_delete();
+
+ // The folder tab should now be showing message 2
+ assert_selected_and_displayed(2);
+
+ // The other displays should now be showing the expectedMessage
+ await _verify_message_is_displayed_in(
+ VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+ }
+);
+
+/**
+ * Test deleting multiple messages in a folder tab (including the last one!),
+ * with message displays open to the beginning of a selection.
+ */
+add_task(
+ async function test_delete_multiple_messages_including_the_last_one_with_first_open() {
+ // 10 messages in this folder. Open up message 1 everywhere.
+ await _open_message_in_all_four_display_mechanisms_helper(
+ multipleDeletionFolder2,
+ 1
+ );
+
+ // We'll select 1-4, 7, 8 and 9. We expect 5 to be the next displayed message.
+ select_click_row(1);
+ select_shift_click_row(4);
+ select_control_click_row(7);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(5);
+
+ // Delete the selected messages
+ press_delete();
+
+ // All the displays should now be showing the expectedMessage
+ await _verify_message_is_displayed_in(VERIFY_ALL, expectedMessage);
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+ }
+);
+
+/**
+ * Test deleting multiple messages in a folder tab (including the last one!),
+ * with message displays open to the middle of a selection.
+ */
+add_task(
+ async function test_delete_multiple_messages_including_the_last_one_with_nth_open() {
+ // 10 messages in this folder. Open up message 7 everywhere.
+ await _open_message_in_all_four_display_mechanisms_helper(
+ multipleDeletionFolder3,
+ 7
+ );
+
+ // We'll select 1-4, 7, 8 and 9. We expect 6 to be the next displayed message.
+ select_click_row(1);
+ select_shift_click_row(4);
+ select_control_click_row(7);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(6);
+
+ // Delete the selected messages
+ press_delete();
+
+ // The folder tab should now be showing message 1
+ assert_selected_and_displayed(1);
+
+ // The other displays should now be showing the expectedMessage
+ await _verify_message_is_displayed_in(
+ VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+ }
+);
+
+/**
+ * Test deleting multiple messages in a folder tab (including the last one!),
+ * with message displays open to the end of a selection.
+ */
+add_task(
+ async function test_delete_multiple_messages_including_the_last_one_with_last_open() {
+ // 10 messages in this folder. Open up message 9 everywhere.
+ await _open_message_in_all_four_display_mechanisms_helper(
+ multipleDeletionFolder4,
+ 9
+ );
+
+ // We'll select 1-4, 7, 8 and 9. We expect 6 to be the next displayed message.
+ select_click_row(1);
+ select_shift_click_row(4);
+ select_control_click_row(7);
+ select_control_click_row(8);
+ select_control_click_row(9);
+ let expectedMessage = get_about_3pane().gDBView.getMsgHdrAt(6);
+
+ // Delete the selected messages
+ press_delete();
+
+ // The folder tab should now be showing message 1
+ assert_selected_and_displayed(1);
+
+ // The other displays should now be showing the expectedMessage
+ await _verify_message_is_displayed_in(
+ VERIFY_MESSAGE_TAB |
+ VERIFY_BACKGROUND_MESSAGE_TAB |
+ VERIFY_MESSAGE_WINDOW,
+ expectedMessage
+ );
+
+ // Clean up, close everything
+ close_message_window(msgc);
+ close_tab(tabMessage);
+ close_tab(tabMessageBackground);
+ await switch_tab(tabFolder);
+ }
+);
diff --git a/comm/mail/test/browser/folder-display/browser_displayName.js b/comm/mail/test/browser/folder-display/browser_displayName.js
new file mode 100644
index 0000000000..5e8cf323b7
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_displayName.js
@@ -0,0 +1,244 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that the display names in email addresses are correctly shown in the
+ * thread pane.
+ */
+
+"use strict";
+
+var { ensure_card_exists } = ChromeUtils.import(
+ "resource://testing-common/mozmill/AddressBookHelpers.jsm"
+);
+var {
+ add_message_to_folder,
+ be_in_folder,
+ create_folder,
+ create_message,
+ get_about_3pane,
+ mc,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder;
+
+var messages = [
+ // Basic From header tests
+ {
+ name: "from_display_name_unquoted",
+ headers: { From: "Carter Burke <cburke@wyutani.invalid>" },
+ expected: { column: "from", value: "Carter Burke" },
+ },
+ {
+ name: "from_display_name_quoted",
+ headers: { From: '"Ellen Ripley" <eripley@wyutani.invalid>' },
+ expected: { column: "from", value: "Ellen Ripley" },
+ },
+ {
+ name: "from_display_name_with_comma",
+ headers: { From: '"William Gorman, Lt." <wgorman@uscmc.invalid>' },
+ expected: { column: "from", value: "William Gorman, Lt." },
+ },
+ {
+ name: "from_email_raw",
+ headers: { From: "dhicks@uscmc.invalid" },
+ expected: { column: "from", value: "dhicks@uscmc.invalid" },
+ },
+ {
+ name: "from_email_in_angle_brackets",
+ headers: { From: "<whudson@uscmc.invalid>" },
+ expected: { column: "from", value: "whudson@uscmc.invalid" },
+ },
+
+ // Basic To header tests
+ {
+ name: "to_display_name_unquoted",
+ headers: { To: "Carter Burke <cburke@wyutani.invalid>" },
+ expected: { column: "recipients", value: "Carter Burke" },
+ },
+ {
+ name: "to_display_name_quoted",
+ headers: { To: '"Ellen Ripley" <eripley@wyutani.invalid>' },
+ expected: { column: "recipients", value: "Ellen Ripley" },
+ },
+ {
+ name: "to_display_name_with_comma",
+ headers: { To: '"William Gorman, Lt." <wgorman@uscmc.invalid>' },
+ expected: { column: "recipients", value: "William Gorman, Lt." },
+ },
+ {
+ name: "to_email_raw",
+ headers: { To: "dhicks@uscmc.invalid" },
+ expected: { column: "recipients", value: "dhicks@uscmc.invalid" },
+ },
+ {
+ name: "to_email_in_angle_brackets",
+ headers: { To: "<whudson@uscmc.invalid>" },
+ expected: { column: "recipients", value: "whudson@uscmc.invalid" },
+ },
+ {
+ name: "to_display_name_multiple",
+ headers: {
+ To:
+ "Carter Burke <cburke@wyutani.invalid>, " +
+ "Dwayne Hicks <dhicks@uscmc.invalid>",
+ },
+ expected: { column: "recipients", value: "Carter Burke, Dwayne Hicks" },
+ },
+
+ // Address book tests
+ {
+ name: "from_in_abook_pdn",
+ headers: { From: "Al Apone <aapone@uscmc.invalid>" },
+ expected: { column: "from", value: "Sarge" },
+ },
+ {
+ name: "from_in_abook_no_pdn",
+ headers: { From: "Rebeccah Jorden <rjorden@hadleys-hope.invalid>" },
+ expected: { column: "from", value: "Rebeccah Jorden" },
+ },
+ {
+ name: "to_in_abook_pdn",
+ headers: { To: "Al Apone <aapone@uscmc.invalid>" },
+ expected: { column: "recipients", value: "Sarge" },
+ },
+ {
+ name: "to_in_abook_no_pdn",
+ headers: { To: "Rebeccah Jorden <rjorden@hadleys-hope.invalid>" },
+ expected: { column: "recipients", value: "Rebeccah Jorden" },
+ },
+ {
+ name: "to_in_abook_multiple_mixed_pdn",
+ headers: {
+ To:
+ "Al Apone <aapone@uscmc.invalid>, " +
+ "Rebeccah Jorden <rjorden@hadleys-hope.invalid>",
+ },
+ expected: { column: "recipients", value: "Sarge, Rebeccah Jorden" },
+ },
+
+ // Esoteric tests; these mainly test that we're getting the expected info back
+ // from the message header.
+ {
+ name: "from_display_name_multiple",
+ headers: {
+ From:
+ "Carter Burke <cburke@wyutani.invalid>, " +
+ "Dwayne Hicks <dhicks@uscmc.invalid>",
+ },
+ expected: { column: "from", value: "Carter Burke et al." },
+ },
+ {
+ name: "from_missing",
+ headers: { From: null },
+ expected: { column: "from", value: "" },
+ },
+ {
+ name: "from_empty",
+ headers: { From: "" },
+ expected: { column: "from", value: "" },
+ },
+ {
+ name: "from_invalid",
+ headers: { From: "invalid" },
+ expected: { column: "from", value: "invalid" },
+ },
+ {
+ name: "from_and_sender_display_name",
+ headers: {
+ From: "Carter Burke <cburke@wyutani.invalid>",
+ Sender: "The Company <thecompany@wyutani.invalid>",
+ },
+ expected: { column: "from", value: "Carter Burke" },
+ },
+ {
+ name: "sender_and_no_from_display_name",
+ headers: { From: null, Sender: "The Company <thecompany@wyutani.invalid>" },
+ expected: { column: "from", value: "The Company" },
+ },
+ {
+ name: "to_missing",
+ headers: { To: null },
+ expected: { column: "recipients", value: "" },
+ },
+ {
+ name: "to_empty",
+ headers: { To: "" },
+ expected: { column: "recipients", value: "" },
+ },
+ {
+ name: "to_invalid",
+ headers: { To: "invalid" },
+ expected: { column: "recipients", value: "invalid" },
+ },
+ {
+ name: "to_and_cc_display_name",
+ headers: {
+ To: "Carter Burke <cburke@wyutani.invalid>",
+ Cc: "The Company <thecompany@wyutani.invalid>",
+ },
+ expected: { column: "recipients", value: "Carter Burke" },
+ },
+ {
+ name: "cc_and_no_to_display_name",
+ headers: { To: null, Cc: "The Company <thecompany@wyutani.invalid>" },
+ expected: { column: "recipients", value: "The Company" },
+ },
+];
+
+var contacts = [
+ { email: "aapone@uscmc.invalid", name: "Sarge", pdn: true },
+ { email: "rjorden@hadleys-hope.invalid", name: "Newt", pdn: false },
+];
+
+add_setup(async function () {
+ folder = await create_folder("DisplayNameA");
+
+ for (let message of messages) {
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ clobberHeaders: message.headers,
+ })
+ );
+ }
+
+ for (let contact of contacts) {
+ ensure_card_exists(contact.email, contact.name, contact.pdn);
+ }
+
+ await be_in_folder(folder);
+});
+
+async function check_display_name(index, columnName, expectedName) {
+ let columnId;
+ switch (columnName) {
+ case "from":
+ columnId = "senderCol";
+ break;
+ case "recipients":
+ columnId = "recipientCol";
+ break;
+ default:
+ throw new Error("unknown column name: " + columnName);
+ }
+
+ let cellText = get_about_3pane().gDBView.cellTextForColumn(index, columnId);
+ Assert.equal(cellText, expectedName, columnName);
+}
+
+// Generate a test for each message in |messages|.
+for (let [i, message] of messages.entries()) {
+ this["test_" + message.name] = async function (i, message) {
+ await check_display_name(
+ i,
+ message.expected.column,
+ message.expected.value
+ );
+ }.bind(this, i, message);
+ add_task(this[`test_${message.name}`]);
+}
diff --git a/comm/mail/test/browser/folder-display/browser_folderPaneVisibility.js b/comm/mail/test/browser/folder-display/browser_folderPaneVisibility.js
new file mode 100644
index 0000000000..0c74031457
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_folderPaneVisibility.js
@@ -0,0 +1,275 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that the folder pane collapses properly, stays collapsed amongst tab
+ * changes, and that persistence works (to a first approximation).
+ */
+
+"use strict";
+
+var {
+ be_in_folder,
+ close_tab,
+ create_folder,
+ get_about_3pane,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_tab,
+ open_selected_message_in_new_tab,
+ select_click_row,
+ switch_tab,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder;
+
+add_setup(async function () {
+ folder = await create_folder("FolderPaneVisibility");
+ await make_message_sets_in_folders([folder], [{ count: 3 }]);
+});
+
+/**
+ * When displaying a folder, assert that the folder pane is visible and all the
+ * menus, splitters, etc. are set up right.
+ */
+function assert_folder_pane_visible() {
+ let win = get_about_3pane();
+
+ Assert.equal(
+ win.paneLayout.folderPaneVisible,
+ true,
+ "The tab does not think that the folder pane is visible, but it should!"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_visible(
+ win.window.document.getElementById("folderTree")
+ ),
+ "The folder tree should not be collapsed!"
+ );
+ Assert.equal(
+ win.folderPaneSplitter.isCollapsed,
+ false,
+ "The folder tree splitter should not be collapsed!"
+ );
+
+ mc.window.view_init(); // Force the view menu to update.
+ let paneMenuItem = mc.window.document.getElementById("menu_showFolderPane");
+ Assert.equal(
+ paneMenuItem.getAttribute("checked"),
+ "true",
+ "The Folder Pane menu item should be checked."
+ );
+}
+
+/**
+ * When displaying a folder, assert that the folder pane is hidden and all the
+ * menus, splitters, etc. are set up right.
+ */
+function assert_folder_pane_hidden() {
+ let win = get_about_3pane();
+
+ Assert.equal(
+ win.paneLayout.folderPaneVisible,
+ false,
+ "The tab thinks that the folder pane is visible, but it shouldn't!"
+ );
+ Assert.ok(
+ BrowserTestUtils.is_hidden(
+ win.window.document.getElementById("folderTree")
+ ),
+ "The folder tree should be collapsed!"
+ );
+ Assert.equal(
+ win.folderPaneSplitter.isCollapsed,
+ true,
+ "The folder tree splitter should be collapsed!"
+ );
+
+ mc.window.view_init(); // Force the view menu to update.
+ let paneMenuItem = mc.window.document.getElementById("menu_showFolderPane");
+ Assert.notEqual(
+ paneMenuItem.getAttribute("checked"),
+ "true",
+ "The Folder Pane menu item should not be checked."
+ );
+}
+
+function toggle_folder_pane() {
+ // Since we don't have a shortcut to toggle the folder pane, we're going to
+ // have to collapse it ourselves
+ get_about_3pane().commandController.doCommand("cmd_toggleFolderPane");
+}
+
+/**
+ * By default, the folder pane should be visible.
+ */
+add_task(async function test_folder_pane_visible_state_is_right() {
+ await be_in_folder(folder);
+ assert_folder_pane_visible();
+});
+
+/**
+ * Toggle the folder pane off.
+ */
+add_task(function test_toggle_folder_pane_off() {
+ toggle_folder_pane();
+ assert_folder_pane_hidden();
+});
+
+/**
+ * Toggle the folder pane on.
+ */
+add_task(function test_toggle_folder_pane_on() {
+ toggle_folder_pane();
+ assert_folder_pane_visible();
+});
+
+/**
+ * Make sure that switching to message tabs of folder tabs with a different
+ * folder pane state does not break. This test should cover all transition
+ * states.
+ */
+add_task(async function test_folder_pane_is_sticky() {
+ Assert.equal(document.getElementById("tabmail").tabInfo.length, 1);
+ let tabFolderA = await be_in_folder(folder);
+ assert_folder_pane_visible();
+
+ // [folder+ => (new) message]
+ select_click_row(0);
+ let tabMessage = await open_selected_message_in_new_tab();
+
+ // [message => folder+]
+ await switch_tab(tabFolderA);
+ assert_folder_pane_visible();
+
+ // [folder+ => (new) folder+]
+ let tabFolderB = await open_folder_in_new_tab(folder);
+ assert_folder_pane_visible();
+
+ // [folder pane toggle + => -]
+ toggle_folder_pane();
+ assert_folder_pane_hidden();
+
+ // [folder- => folder+]
+ await switch_tab(tabFolderA);
+ assert_folder_pane_visible();
+
+ // (redundant) [ folder pane toggle + => -]
+ toggle_folder_pane();
+ assert_folder_pane_hidden();
+
+ // [folder- => message]
+ await switch_tab(tabMessage);
+
+ // [message => folder-]
+ close_tab(tabMessage);
+ assert_folder_pane_hidden();
+
+ // the tab we are on now doesn't matter, so we don't care
+ assert_folder_pane_hidden();
+ await switch_tab(tabFolderB);
+
+ // [ folder pane toggle - => + ]
+ toggle_folder_pane();
+ assert_folder_pane_visible();
+
+ // [folder+ => folder-]
+ close_tab(tabFolderB);
+ assert_folder_pane_hidden();
+
+ // (redundant) [ folder pane toggle - => + ]
+ toggle_folder_pane();
+ assert_folder_pane_visible();
+});
+
+/**
+ * Test that if we serialize and restore the tabs then the folder pane is in the
+ * expected collapsed/non-collapsed state. Because of the special "first tab"
+ * situation, we need to do this twice to test each case for the first tab. For
+ * additional thoroughness we also flip the state we have the other tabs be in.
+ */
+add_task(async function test_folder_pane_persistence_generally_works() {
+ await be_in_folder(folder);
+
+ let tabmail = mc.window.document.getElementById("tabmail");
+
+ // helper to open tabs with the folder pane in the desired states (1 for
+ // visible, 0 for hidden)
+ async function openTabs(aConfig) {
+ for (let [iTab, folderPaneVisible] of aConfig.entries()) {
+ if (iTab != 0) {
+ await open_folder_in_new_tab(folder);
+ }
+ if (
+ tabmail.currentAbout3Pane.paneLayout.folderPaneVisible !=
+ folderPaneVisible
+ ) {
+ toggle_folder_pane();
+ }
+ }
+ }
+
+ // close everything but the first tab.
+ function closeTabs() {
+ while (tabmail.tabInfo.length > 1) {
+ tabmail.closeTab(1);
+ }
+ }
+
+ async function verifyTabs(aConfig) {
+ for (let [iTab, folderPaneVisible] of aConfig.entries()) {
+ info("tab " + iTab);
+
+ await switch_tab(iTab);
+ if (tabmail.currentAbout3Pane.document.readyState != "complete") {
+ await BrowserTestUtils.waitForEvent(tabmail.currentAbout3Pane, "load");
+ await new Promise(resolve =>
+ tabmail.currentAbout3Pane.setTimeout(resolve)
+ );
+ }
+
+ if (folderPaneVisible) {
+ assert_folder_pane_visible();
+ } else {
+ assert_folder_pane_hidden();
+ }
+ }
+ }
+
+ let configs = [
+ // 1st time: [+ - - + +]
+ [1, 0, 0, 1, 1],
+ // 2nd time: [- + + - -]
+ [0, 1, 1, 0, 0],
+ ];
+
+ for (let config of configs) {
+ await openTabs(config);
+ await verifyTabs(config); // make sure openTabs did its job right
+ let state = tabmail.persistTabs();
+ closeTabs();
+
+ Assert.equal(state.tabs[0].state.folderPaneVisible, config[0]);
+ Assert.equal(state.tabs[1].state.folderPaneVisible, config[1]);
+ Assert.equal(state.tabs[2].state.folderPaneVisible, config[2]);
+ Assert.equal(state.tabs[3].state.folderPaneVisible, config[3]);
+ Assert.equal(state.tabs[4].state.folderPaneVisible, config[4]);
+
+ // toggle the state for the current tab so we can be sure that it knows how
+ // to change things.
+ toggle_folder_pane();
+
+ tabmail.restoreTabs(state);
+ await verifyTabs(config);
+ closeTabs();
+
+ // toggle the first tab again. This sets closed properly for the second pass and
+ // restores it to open for when we are done.
+ toggle_folder_pane();
+ }
+ // For one last time, make sure.
+ assert_folder_pane_visible();
+});
diff --git a/comm/mail/test/browser/folder-display/browser_folderToolbar.js b/comm/mail/test/browser/folder-display/browser_folderToolbar.js
new file mode 100644
index 0000000000..41365f55c4
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_folderToolbar.js
@@ -0,0 +1,147 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that opening new folder and message tabs has the expected result and
+ * that closing them doesn't break anything.
+ */
+
+"use strict";
+
+var {
+ add_to_toolbar,
+ assert_folder_selected_and_displayed,
+ assert_nothing_selected,
+ be_in_folder,
+ close_tab,
+ create_folder,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_tab,
+ open_selected_message_in_new_tab,
+ remove_from_toolbar,
+ select_click_row,
+ switch_tab,
+ wait_for_blank_content_pane,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folderA, folderB;
+
+add_setup(async function () {
+ folderA = await create_folder("FolderToolbarA");
+ // we need one message to select and open
+ folderB = await create_folder("FolderToolbarB");
+ await make_message_sets_in_folders([folderB], [{ count: 1 }]);
+});
+
+add_task(function test_add_folder_toolbar() {
+ // It should not be present by default
+ let folderLoc = mc.window.document.getElementById("locationFolders");
+ Assert.ok(!folderLoc);
+
+ // But it should show up when we call
+ add_to_toolbar(
+ mc.window.document.getElementById("mail-bar3"),
+ "folder-location-container"
+ );
+ folderLoc = mc.window.document.getElementById("locationFolders");
+ Assert.ok(folderLoc);
+
+ Assert.equal(
+ !!folderLoc.label,
+ true,
+ "Uninitialized Folder doesn't have a default label."
+ );
+});
+
+add_task(async function test_folder_toolbar_shows_correct_item() {
+ add_to_toolbar(
+ mc.window.document.getElementById("mail-bar3"),
+ "folder-location-container"
+ );
+ let folderLoc = mc.window.document.getElementById("locationFolders");
+
+ // Start in folder a.
+ let tabFolderA = await be_in_folder(folderA);
+ assert_folder_selected_and_displayed(folderA);
+ assert_nothing_selected();
+ Assert.equal(
+ folderLoc.label,
+ "FolderToolbarA",
+ "Opening FolderA doesn't update toolbar."
+ );
+
+ // Open tab b, make sure it works right.
+ let tabFolderB = await open_folder_in_new_tab(folderB);
+ wait_for_blank_content_pane();
+ assert_folder_selected_and_displayed(folderB);
+ assert_nothing_selected();
+ Assert.equal(
+ folderLoc.label,
+ "FolderToolbarB",
+ "Opening FolderB in a tab doesn't update toolbar."
+ );
+
+ // Go back to tab/folder A and make sure we change correctly.
+ await switch_tab(tabFolderA);
+ assert_folder_selected_and_displayed(folderA);
+ assert_nothing_selected();
+ Assert.equal(
+ folderLoc.label,
+ "FolderToolbarA",
+ "Switching back to FolderA's tab doesn't update toolbar."
+ );
+
+ // Go back to tab/folder A and make sure we change correctly.
+ await switch_tab(tabFolderB);
+ assert_folder_selected_and_displayed(folderB);
+ assert_nothing_selected();
+ Assert.equal(
+ folderLoc.label,
+ "FolderToolbarB",
+ "Switching back to FolderB's tab doesn't update toolbar."
+ );
+ close_tab(tabFolderB);
+});
+
+add_task(async function test_folder_toolbar_disappears_on_message_tab() {
+ add_to_toolbar(
+ mc.window.document.getElementById("mail-bar3"),
+ "folder-location-container"
+ );
+ await be_in_folder(folderB);
+ let folderLoc = mc.window.document.getElementById("locationFolders");
+ Assert.ok(folderLoc);
+ Assert.equal(
+ folderLoc.label,
+ "FolderToolbarB",
+ "We should have started in FolderB."
+ );
+ Assert.equal(folderLoc.collapsed, false, "The toolbar should be shown.");
+
+ // Select one message
+ select_click_row(0);
+ // Open it
+ let messageTab = await open_selected_message_in_new_tab();
+
+ Assert.equal(
+ mc.window.document.getElementById("folder-location-container").collapsed,
+ true,
+ "The toolbar should be hidden."
+ );
+
+ // Clean up, close the tab
+ close_tab(messageTab);
+});
+
+add_task(function test_remove_folder_toolbar() {
+ remove_from_toolbar(
+ mc.window.document.getElementById("mail-bar3"),
+ "folder-location-container"
+ );
+
+ Assert.ok(!mc.window.document.getElementById("locationFolders"));
+});
diff --git a/comm/mail/test/browser/folder-display/browser_invalidDbFolderLoad.js b/comm/mail/test/browser/folder-display/browser_invalidDbFolderLoad.js
new file mode 100644
index 0000000000..61ccf08d08
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_invalidDbFolderLoad.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that clicking on a folder with an invalid or missing .msf file
+ * regenerates the.msf file and loads the view.
+ * Also, check that rebuilding the index on a loaded folder reloads the folder.
+ */
+
+"use strict";
+
+var {
+ assert_messages_in_view,
+ assert_selected_and_displayed,
+ be_in_folder,
+ create_folder,
+ get_about_3pane,
+ make_message_sets_in_folders,
+ mc,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder;
+var setA;
+
+add_setup(async function () {
+ folder = await create_folder("InvalidMSF");
+ [setA] = await make_message_sets_in_folders([folder], [{ count: 3 }]);
+});
+
+/**
+ * Check if the db of a folder assumed to be invalid can be restored.
+ */
+add_task(async function test_load_folder_with_invalidDB() {
+ folder.msgDatabase.dBFolderInfo.sortType = Ci.nsMsgViewSortType.bySubject;
+ folder.msgDatabase.summaryValid = false;
+ folder.msgDatabase.forceClosed();
+ folder.msgDatabase = null;
+ await be_in_folder(folder);
+
+ assert_messages_in_view(setA);
+ var curMessage = select_click_row(0);
+ assert_selected_and_displayed(curMessage);
+});
+
+add_task(function test_view_sort_maintained() {
+ let win = get_about_3pane();
+ if (win.gDBView.sortType != Ci.nsMsgViewSortType.bySubject) {
+ throw new Error("view sort type not restored from invalid db");
+ }
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_mailTelemetry.js b/comm/mail/test/browser/folder-display/browser_mailTelemetry.js
new file mode 100644
index 0000000000..d2d73627bb
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_mailTelemetry.js
@@ -0,0 +1,135 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test telemetry related to secure mails read.
+ */
+
+let {
+ create_folder,
+ be_in_folder,
+ create_message,
+ create_encrypted_smime_message,
+ create_encrypted_openpgp_message,
+ add_message_to_folder,
+ select_click_row,
+ assert_selected_and_displayed,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+let { SmimeUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/smimeUtils.jsm"
+);
+let { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+add_setup(function () {
+ SmimeUtils.ensureNSS();
+ SmimeUtils.loadCertificateAndKey(
+ new FileUtils.File(getTestFilePath("../openpgp/data/smime/Bob.p12")),
+ "nss"
+ );
+});
+
+/**
+ * Check that we're counting secure mails read.
+ */
+add_task(async function test_secure_mails_read() {
+ Services.telemetry.clearScalars();
+
+ const NUM_PLAIN_MAILS = 4;
+ const NUM_SMIME_MAILS = 2;
+ const NUM_OPENPGP_MAILS = 3;
+ let headers = { from: "alice@t1.example.com", to: "bob@t2.example.net" };
+ let folder = await create_folder("secure-mail");
+
+ // normal message should not be counted
+ for (let i = 0; i < NUM_PLAIN_MAILS; i++) {
+ await add_message_to_folder(
+ [folder],
+ create_message({
+ clobberHeaders: headers,
+ })
+ );
+ }
+ for (let i = 0; i < NUM_SMIME_MAILS; i++) {
+ await add_message_to_folder(
+ [folder],
+ create_encrypted_smime_message({
+ to: "Bob@example.com",
+ body: {
+ body: smimeMessage,
+ },
+ })
+ );
+ }
+ for (let i = 0; i < NUM_OPENPGP_MAILS; i++) {
+ await add_message_to_folder(
+ [folder],
+ create_encrypted_openpgp_message({
+ clobberHeaders: headers,
+ })
+ );
+ }
+
+ // Select (read) all added mails.
+ await be_in_folder(folder);
+ for (
+ let i = 0;
+ i < NUM_PLAIN_MAILS + NUM_SMIME_MAILS + NUM_OPENPGP_MAILS;
+ i++
+ ) {
+ select_click_row(i);
+ }
+
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ Assert.equal(
+ scalars["tb.mails.read_secure"]["encrypted-smime"],
+ NUM_SMIME_MAILS,
+ "Count of smime encrypted mails read must be correct."
+ );
+ Assert.equal(
+ scalars["tb.mails.read_secure"]["encrypted-openpgp"],
+ NUM_OPENPGP_MAILS,
+ "Count of openpgp encrypted mails read must be correct."
+ );
+
+ // Select all added mails again should not change read statistics.
+ for (
+ let i = 0;
+ i < NUM_PLAIN_MAILS + NUM_SMIME_MAILS + NUM_OPENPGP_MAILS;
+ i++
+ ) {
+ select_click_row(i);
+ }
+
+ scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ Assert.equal(
+ scalars["tb.mails.read_secure"]["encrypted-smime"],
+ NUM_SMIME_MAILS,
+ "Count of smime encrypted mails read must still be correct."
+ );
+ Assert.equal(
+ scalars["tb.mails.read_secure"]["encrypted-openpgp"],
+ NUM_OPENPGP_MAILS,
+ "Count of openpgp encrypted mails read must still be correct."
+ );
+});
+
+var smimeMessage = [
+ "MIAGCSqGSIb3DQEHA6CAMIACAQAxggGFMIIBgQIBADBpMGQxCzAJBgNVBAYTAlVT",
+ "MRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRIw",
+ "EAYDVQQKEwlCT0dVUyBOU1MxFDASBgNVBAMTC05TUyBUZXN0IENBAgEoMA0GCSqG",
+ "SIb3DQEBAQUABIIBAByaXGnoQAgRiPjvcpotJWBQwXjAxYldgMaT/hEX0Hlnas6m",
+ "OcBIOJLB9CHhmBOSo/yryDOnRcl9l1cQYzSEpExYSGoVzPCpPOLKw5C/A+6NFzpe",
+ "44EUX5/gVbVeQ4fl2dOB3NbW5Cnx3Js7O1MFr8UPFOh31TBhvWjOMl+3CkMWndUi",
+ "G4C/srgdeuQRdKJcWoROtBjQuibVHfn0TcA7olIj8ysmJoTT3Irx625Sh5mDDVbJ",
+ "UyR2WWqw6wPAaCS2urUXtYrEuxsr7EmdcZc0P6oikzf/KoMvzBWBmWJXad1QSdeO",
+ "s5Bk2MYKXoM9Iqddr/n9mvg4jJNnFMzG0cFKCAgwgAYJKoZIhvcNAQcBMB0GCWCG",
+ "SAFlAwQBAgQQ2QrTbolonzr0vAfmGH2nJ6CABIGQKA2mKyOQShspbeDIf/QlYHg+",
+ "YbiqdhlENHHM5V5rICjM5LFzLME0TERDJGi8tATlqp3rFOswFDGiymK6XZrpQZiW",
+ "TBTEa2E519Mw86NEJ1d/iy4aLpPjATH2rhZLm3dix42mFI5ToszGNu9VuDWDiV4S",
+ "sA798v71TaSlFwh9C3VwODQ8lWwyci4aD3wdxevGBBC3fYMuEns+NIQhqpzlUADX",
+ "AAAAAAAAAAAAAA==",
+].join("\n");
diff --git a/comm/mail/test/browser/folder-display/browser_mailViews.js b/comm/mail/test/browser/folder-display/browser_mailViews.js
new file mode 100644
index 0000000000..e02f8e5e53
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_mailViews.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var {
+ assert_messages_in_view,
+ be_in_folder,
+ create_folder,
+ make_message_sets_in_folders,
+ mc,
+ wait_for_all_messages_to_load,
+ get_about_3pane,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { plan_for_modal_dialog, wait_for_modal_dialog } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+var { MailViewConstants } = ChromeUtils.import(
+ "resource:///modules/MailViewManager.jsm"
+);
+
+const { storeState } = ChromeUtils.importESModule(
+ "resource:///modules/CustomizationState.mjs"
+);
+
+var baseFolder, savedFolder;
+var setUntagged, setTagged;
+
+add_setup(async function () {
+ // Create a folder with some messages that have no tags and some that are
+ // tagged Important ($label1).
+ baseFolder = await create_folder("MailViewA");
+ [setUntagged, setTagged] = await make_message_sets_in_folders(
+ [baseFolder],
+ [{}, {}]
+ );
+ setTagged.addTag("$label1"); // Important, by default
+ storeState({
+ mail: ["view-picker"],
+ });
+ await BrowserTestUtils.waitForMutationCondition(
+ document.getElementById("unifiedToolbarContent"),
+ {
+ subtree: true,
+ childList: true,
+ },
+ () => document.querySelector("#unifiedToolbarContent .view-picker")
+ );
+
+ registerCleanupFunction(() => {
+ storeState({});
+ });
+});
+
+add_task(function test_put_view_picker_on_toolbar() {
+ Assert.ok(
+ window.ViewPickerBinding.isVisible,
+ "View picker is registered as visible"
+ );
+});
+
+/**
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c97
+ */
+add_task(async function test_save_view_as_folder() {
+ // - enter the folder
+ await be_in_folder(baseFolder);
+
+ // - apply the mail view
+ // okay, mozmill is just not ready to click on the view picker...
+ // just call the ViewChange global. it's sad, but it has the same effects.
+ // at least, it does once we've caused the popups to get refreshed.
+ mc.window.RefreshAllViewPopups(
+ mc.window.document.getElementById("toolbarViewPickerPopup")
+ );
+ mc.window.ViewChange(":$label1");
+ wait_for_all_messages_to_load();
+
+ // - save it
+ plan_for_modal_dialog(
+ "mailnews:virtualFolderProperties",
+ subtest_save_mail_view
+ );
+ // we have to use value here because the option mechanism is not sophisticated
+ // enough.
+ mc.window.ViewChange(MailViewConstants.kViewItemVirtual);
+ wait_for_modal_dialog("mailnews:virtualFolderProperties");
+});
+
+function subtest_save_mail_view(savc) {
+ // - make sure the name is right
+ Assert.equal(
+ savc.window.document.getElementById("name").value,
+ baseFolder.prettyName + "-Important"
+ );
+
+ let selector = savc.window.document.querySelector("#searchVal0 menulist");
+ Assert.ok(selector, "Should have a tag selector");
+
+ // Check the value of the search-value.
+ Assert.equal(selector.value, "$label1");
+
+ // - save it
+ savc.window.document.querySelector("dialog").acceptDialog();
+}
+
+add_task(async function test_verify_saved_mail_view() {
+ // - make sure the folder got created
+ savedFolder = baseFolder.getChildNamed(baseFolder.prettyName + "-Important");
+ if (!savedFolder) {
+ throw new Error("MailViewA-Important was not created!");
+ }
+
+ // - go in the folder and make sure the right messages are displayed
+ await be_in_folder(savedFolder);
+ assert_messages_in_view(setTagged, mc);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_messageCommands.js b/comm/mail/test/browser/folder-display/browser_messageCommands.js
new file mode 100644
index 0000000000..ab49d07df3
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_messageCommands.js
@@ -0,0 +1,802 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This tests various commands on messages. This is primarily for commands
+ * that can't be tested with xpcshell tests because they're handling in the
+ * front end - which is why Archive is the only command currently tested.
+ */
+
+"use strict";
+
+var { wait_for_content_tab_load } = ChromeUtils.import(
+ "resource://testing-common/mozmill/ContentTabHelpers.jsm"
+);
+var {
+ add_message_sets_to_folders,
+ archive_selected_messages,
+ assert_selected_and_displayed,
+ be_in_folder,
+ close_popup,
+ collapse_all_threads,
+ create_folder,
+ create_thread,
+ get_about_3pane,
+ get_about_message,
+ make_display_threaded,
+ make_display_unthreaded,
+ make_message_sets_in_folders,
+ mc,
+ press_delete,
+ right_click_on_row,
+ select_click_row,
+ select_control_click_row,
+ select_shift_click_row,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { click_menus_in_sequence } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var unreadFolder, shiftDeleteFolder, threadDeleteFolder;
+var archiveSrcFolder = null;
+
+var tagArray;
+var gAutoRead;
+
+// Adjust timeout to take care of code coverage runs needing twice as long.
+requestLongerTimeout(AppConstants.MOZ_CODE_COVERAGE ? 2 : 1);
+
+add_setup(async function () {
+ gAutoRead = Services.prefs.getBoolPref("mailnews.mark_message_read.auto");
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", false);
+
+ unreadFolder = await create_folder("UnreadFolder");
+ shiftDeleteFolder = await create_folder("ShiftDeleteFolder");
+ threadDeleteFolder = await create_folder("ThreadDeleteFolder");
+ archiveSrcFolder = await create_folder("ArchiveSrc");
+
+ await make_message_sets_in_folders([unreadFolder], [{ count: 2 }]);
+ await make_message_sets_in_folders([shiftDeleteFolder], [{ count: 3 }]);
+ await add_message_sets_to_folders(
+ [threadDeleteFolder],
+ [create_thread(3), create_thread(3), create_thread(3)]
+ );
+
+ // Create messages from 20 different months, which will mean 2 different
+ // years as well.
+ await make_message_sets_in_folders(
+ [archiveSrcFolder],
+ [{ count: 20, age_incr: { weeks: 5 } }]
+ );
+
+ tagArray = MailServices.tags.getAllTags();
+});
+
+/**
+ * Ensures that all messages have a particular read status
+ *
+ * @param messages an array of nsIMsgDBHdrs to check
+ * @param read true if the messages should be marked read, false otherwise
+ */
+function check_read_status(messages, read) {
+ function read_str(read) {
+ return read ? "read" : "unread";
+ }
+
+ for (let i = 0; i < messages.length; i++) {
+ Assert.ok(
+ messages[i].isRead == read,
+ "Message marked as " +
+ read_str(messages[i].isRead) +
+ ", expected " +
+ read_str(read)
+ );
+ }
+}
+
+/**
+ * Ensures that the mark read/unread menu items are enabled/disabled properly
+ *
+ * @param index the row in the thread pane of the message to query
+ * @param canMarkRead true if the mark read item should be enabled
+ * @param canMarkUnread true if the mark unread item should be enabled
+ */
+async function check_read_menuitems(index, canMarkRead, canMarkUnread) {
+ await right_click_on_row(index);
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ await click_menus_in_sequence(getMailContext(), [{ id: "mailContext-mark" }]);
+
+ let readEnabled = !getMailContext().querySelector("#mailContext-markRead")
+ .disabled;
+ let unreadEnabled = !getMailContext().querySelector("#mailContext-markUnread")
+ .disabled;
+
+ Assert.ok(
+ readEnabled == canMarkRead,
+ "Mark read menu item " +
+ (canMarkRead ? "dis" : "en") +
+ "abled when it shouldn't be!"
+ );
+
+ Assert.ok(
+ unreadEnabled == canMarkUnread,
+ "Mark unread menu item " +
+ (canMarkUnread ? "dis" : "en") +
+ "abled when it shouldn't be!"
+ );
+
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+}
+
+function enable_archiving(enabled) {
+ Services.prefs.setBoolPref("mail.identity.default.archive_enabled", enabled);
+}
+
+/**
+ * Mark a message read or unread via the context menu
+ *
+ * @param index the row in the thread pane of the message to mark read/unread
+ * @param read true the message should be marked read, false otherwise
+ */
+async function mark_read_via_menu(index, read) {
+ let menuItem = read ? "mailContext-markRead" : "mailContext-markUnread";
+ await right_click_on_row(index);
+ await wait_for_popup_to_open(getMailContext());
+ await click_menus_in_sequence(getMailContext(), [
+ { id: "mailContext-mark" },
+ { id: menuItem },
+ ]);
+ await close_popup(mc, getMailContext());
+}
+
+add_task(async function test_mark_one_read() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ curMessage.markRead(false);
+ await mark_read_via_menu(0, true);
+ check_read_status([curMessage], true);
+});
+
+add_task(async function test_mark_one_unread() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ curMessage.markRead(true);
+ await mark_read_via_menu(0, false);
+ check_read_status([curMessage], false);
+});
+
+add_task(async function test_mark_n_read() {
+ await be_in_folder(unreadFolder);
+ select_click_row(0);
+ let curMessages = select_shift_click_row(1);
+
+ for (let i = 0; i < curMessages.length; i++) {
+ curMessages[i].markRead(false);
+ }
+ await mark_read_via_menu(0, true);
+ check_read_status(curMessages, true);
+});
+
+add_task(async function test_mark_n_unread() {
+ await be_in_folder(unreadFolder);
+ select_click_row(0);
+ let curMessages = select_shift_click_row(1);
+
+ for (let i = 0; i < curMessages.length; i++) {
+ curMessages[i].markRead(true);
+ }
+ await mark_read_via_menu(0, false);
+ check_read_status(curMessages, false);
+});
+
+add_task(async function test_mark_n_read_mixed() {
+ await be_in_folder(unreadFolder);
+ select_click_row(0);
+ let curMessages = select_shift_click_row(1);
+
+ curMessages[0].markRead(true);
+ curMessages[1].markRead(false);
+ await mark_read_via_menu(0, true);
+ check_read_status(curMessages, true);
+
+ curMessages[0].markRead(false);
+ curMessages[1].markRead(true);
+ await mark_read_via_menu(0, true);
+ check_read_status(curMessages, true);
+});
+
+add_task(async function test_mark_n_unread_mixed() {
+ await be_in_folder(unreadFolder);
+ select_click_row(0);
+ let curMessages = select_shift_click_row(1);
+
+ curMessages[0].markRead(false);
+ curMessages[1].markRead(true);
+ await mark_read_via_menu(0, false);
+ check_read_status(curMessages, false);
+
+ curMessages[0].markRead(true);
+ curMessages[1].markRead(false);
+ await mark_read_via_menu(0, false);
+ check_read_status(curMessages, false);
+});
+
+add_task(async function test_toggle_read() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ curMessage.markRead(false);
+ EventUtils.synthesizeKey("m", {});
+ check_read_status([curMessage], true);
+});
+
+add_task(async function test_toggle_unread() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ curMessage.markRead(true);
+ EventUtils.synthesizeKey("m", {});
+ check_read_status([curMessage], false);
+});
+
+add_task(async function test_toggle_mixed() {
+ await be_in_folder(unreadFolder);
+ select_click_row(0);
+ let curMessages = select_shift_click_row(1);
+
+ curMessages[0].markRead(false);
+ curMessages[1].markRead(true);
+ EventUtils.synthesizeKey("m", {});
+ check_read_status(curMessages, true);
+
+ curMessages[0].markRead(true);
+ curMessages[1].markRead(false);
+ EventUtils.synthesizeKey("m", {});
+ check_read_status(curMessages, false);
+});
+
+add_task(async function test_mark_menu_read() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ curMessage.markRead(false);
+ await check_read_menuitems(0, true, false);
+});
+
+add_task(async function test_mark_menu_unread() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ curMessage.markRead(true);
+ await check_read_menuitems(0, false, true);
+});
+
+add_task(async function test_mark_menu_mixed() {
+ await be_in_folder(unreadFolder);
+ select_click_row(0);
+ let curMessages = select_shift_click_row(1);
+
+ curMessages[0].markRead(false);
+ curMessages[1].markRead(true);
+
+ await check_read_menuitems(0, true, true);
+});
+
+add_task(async function test_mark_all_read() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+ curMessage.markRead(false);
+
+ // Make sure we can mark all read with >0 messages unread.
+ await right_click_on_row(0);
+ await wait_for_popup_to_open(getMailContext());
+ await click_menus_in_sequence(getMailContext(), [
+ { id: "mailContext-mark" },
+ { id: "mailContext-markAllRead" },
+ ]);
+ await close_popup(mc, getMailContext());
+
+ Assert.ok(curMessage.isRead, "Message should have been marked read!");
+
+ // Make sure we can't mark all read, now that all messages are already read.
+ await right_click_on_row(0);
+ await wait_for_popup_to_open(getMailContext());
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ await click_menus_in_sequence(getMailContext(), [{ id: "mailContext-mark" }]);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ let allReadDisabled = getMailContext().querySelector(
+ "#mailContext-markAllRead"
+ ).disabled;
+ Assert.ok(allReadDisabled, "Mark All Read menu item should be disabled!");
+});
+
+add_task(async function test_mark_thread_as_read() {
+ let unreadThreadFolder = await create_folder("UnreadThreadFolder");
+ await add_message_sets_to_folders([unreadThreadFolder], [create_thread(3)]);
+ await be_in_folder(unreadThreadFolder);
+ make_display_threaded();
+
+ let serviceState = Services.prefs.getBoolPref(
+ "mailnews.mark_message_read.auto"
+ );
+ if (serviceState) {
+ // If mailnews.mark_message_read.auto is true, then we set it to false.
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", false);
+ }
+
+ // Make sure Mark Thread as Read is enabled with >0 messages in thread unread.
+ await right_click_on_row(0);
+ await wait_for_popup_to_open(getMailContext());
+ await click_menus_in_sequence(getMailContext(), [{ id: "mailContext-mark" }]);
+
+ let markThreadAsReadDisabled = mc.window.document.getElementById(
+ "mailContext-markThreadAsRead"
+ ).disabled;
+ Assert.ok(
+ !markThreadAsReadDisabled,
+ "Mark Thread as read menu item should not be disabled!"
+ );
+
+ // Make sure messages are read when Mark Thread as Read is clicked.
+ await right_click_on_row(0);
+ await wait_for_popup_to_open(getMailContext());
+ await click_menus_in_sequence(getMailContext(), [
+ { id: "mailContext-mark" },
+ { id: "mailContext-markThreadAsRead" },
+ ]);
+ await close_popup(mc, getMailContext());
+
+ let curMessage = select_click_row(0);
+ Assert.ok(curMessage.isRead, "Message should have been marked read!");
+
+ // Make sure Mark Thread as Read is now disabled with all messages read.
+ await right_click_on_row(0);
+ await wait_for_popup_to_open(getMailContext());
+ await click_menus_in_sequence(getMailContext(), [{ id: "mailContext-mark" }]);
+
+ markThreadAsReadDisabled = mc.window.document.getElementById(
+ "mailContext-markThreadAsRead"
+ ).disabled;
+ Assert.ok(
+ markThreadAsReadDisabled,
+ "Mark Thread as read menu item should be disabled!"
+ );
+
+ // Make sure that adding an unread message enables Mark Thread as Read once more.
+ curMessage.markRead(false);
+ await right_click_on_row(0);
+ await wait_for_popup_to_open(getMailContext());
+ await click_menus_in_sequence(getMailContext(), [{ id: "mailContext-mark" }]);
+
+ markThreadAsReadDisabled = mc.window.document.getElementById(
+ "mailContext-markThreadAsRead"
+ ).disabled;
+ Assert.ok(
+ !markThreadAsReadDisabled,
+ "Mark Thread as read menu item should not be disabled!"
+ );
+
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", true);
+}).__skipMe = true; // See bug 654362.
+
+add_task(async function roving_multi_message_buttons() {
+ await be_in_folder(unreadFolder);
+ select_click_row(0);
+ let curMessages = select_shift_click_row(1);
+ assert_selected_and_displayed(curMessages);
+
+ let multiMsgView = get_about_3pane().multiMessageBrowser;
+ const BUTTONS_SELECTOR = `toolbarbutton:not([hidden="true"]`;
+ let headerToolbar = multiMsgView.contentDocument.getElementById(
+ "header-view-toolbar"
+ );
+ let headerButtons = headerToolbar.querySelectorAll(BUTTONS_SELECTOR);
+
+ // Press tab twice while on the message selected to access the multi message
+ // view header buttons.
+ EventUtils.synthesizeKey("KEY_Tab", {});
+ EventUtils.synthesizeKey("KEY_Tab", {});
+ Assert.equal(
+ headerButtons[0].id,
+ multiMsgView.contentDocument.activeElement.id,
+ "focused on first msgHdr toolbar button"
+ );
+
+ // Simulate the Arrow Right keypress to make sure the correct button gets the
+ // focus.
+ for (let i = 1; i < headerButtons.length; i++) {
+ let previousElement = document.activeElement;
+ EventUtils.synthesizeKey("KEY_ArrowRight", {});
+ Assert.equal(
+ multiMsgView.contentDocument.activeElement.id,
+ headerButtons[i].id,
+ "The next button is focused"
+ );
+ Assert.ok(
+ multiMsgView.contentDocument.activeElement.tabIndex == 0 &&
+ previousElement.tabIndex == -1,
+ "The roving tab index was updated"
+ );
+ }
+
+ // Simulate the Arrow Left keypress to make sure the correct button gets the
+ // focus.
+ for (let i = headerButtons.length - 2; i > -1; i--) {
+ let previousElement = document.activeElement;
+ EventUtils.synthesizeKey("KEY_ArrowLeft", {});
+ Assert.equal(
+ multiMsgView.contentDocument.activeElement.id,
+ headerButtons[i].id,
+ "The previous button is focused"
+ );
+ Assert.ok(
+ multiMsgView.contentDocument.activeElement.tabIndex == 0 &&
+ previousElement.tabIndex == -1,
+ "The roving tab index was updated"
+ );
+ }
+
+ // Check that once the Escape key is pressed twice, focus will move back to
+ // the selected messages.
+ EventUtils.synthesizeKey("KEY_Escape", {});
+ EventUtils.synthesizeKey("KEY_Escape", {});
+ assert_selected_and_displayed(curMessages);
+}).__skipMe = AppConstants.platform == "macosx";
+
+add_task(async function test_shift_delete_prompt() {
+ await be_in_folder(shiftDeleteFolder);
+ let curMessage = select_click_row(0);
+ goUpdateCommand("cmd_shiftDelete");
+
+ // First, try shift-deleting and then cancelling at the prompt.
+ Services.prefs.setBoolPref("mail.warn_on_shift_delete", true);
+ let dialogPromise = BrowserTestUtils.promiseAlertDialog("cancel");
+ // We don't use press_delete here because we're not actually deleting this
+ // time!
+ SimpleTest.ignoreAllUncaughtExceptions(true);
+ EventUtils.synthesizeKey("VK_DELETE", { shiftKey: true });
+ SimpleTest.ignoreAllUncaughtExceptions(false);
+ await dialogPromise;
+ // Make sure we didn't actually delete the message.
+ Assert.equal(curMessage, select_click_row(0));
+
+ // Second, try shift-deleting and then accepting the deletion.
+ dialogPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ press_delete(mc, { shiftKey: true });
+ await dialogPromise;
+ // Make sure we really did delete the message.
+ Assert.notEqual(curMessage, select_click_row(0));
+
+ // Finally, try shift-deleting when we turned off the prompt.
+ Services.prefs.setBoolPref("mail.warn_on_shift_delete", false);
+ curMessage = select_click_row(0);
+ press_delete(mc, { shiftKey: true });
+
+ // Make sure we really did delete the message.
+ Assert.notEqual(curMessage, select_click_row(0));
+
+ Services.prefs.clearUserPref("mail.warn_on_shift_delete");
+});
+
+add_task(async function test_thread_delete_prompt() {
+ await be_in_folder(threadDeleteFolder);
+ make_display_threaded();
+ collapse_all_threads();
+
+ let curMessage = select_click_row(0);
+ goUpdateCommand("cmd_delete");
+ // First, try deleting and then cancelling at the prompt.
+ Services.prefs.setBoolPref("mail.warn_on_collapsed_thread_operation", true);
+ let dialogPromise = BrowserTestUtils.promiseAlertDialog("cancel");
+ // We don't use press_delete here because we're not actually deleting this
+ // time!
+ SimpleTest.ignoreAllUncaughtExceptions(true);
+ EventUtils.synthesizeKey("VK_DELETE", {});
+ SimpleTest.ignoreAllUncaughtExceptions(false);
+ await dialogPromise;
+ // Make sure we didn't actually delete the message.
+ Assert.equal(curMessage, select_click_row(0));
+
+ // Second, try deleting and then accepting the deletion.
+ dialogPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ press_delete(mc);
+ await dialogPromise;
+ // Make sure we really did delete the message.
+ Assert.notEqual(curMessage, select_click_row(0));
+
+ // Finally, try shift-deleting when we turned off the prompt.
+ Services.prefs.setBoolPref("mail.warn_on_collapsed_thread_operation", false);
+ curMessage = select_click_row(0);
+ press_delete(mc);
+
+ // Make sure we really did delete the message.
+ Assert.notEqual(curMessage, select_click_row(0));
+
+ Services.prefs.clearUserPref("mail.warn_on_collapsed_thread_operation");
+}).skip(); // TODO: not working
+
+add_task(async function test_yearly_archive() {
+ await yearly_archive(false);
+});
+
+async function yearly_archive(keep_structure) {
+ await be_in_folder(archiveSrcFolder);
+ make_display_unthreaded();
+
+ let win = get_about_3pane();
+ win.sortController.sortThreadPane("byDate");
+ win.sortController.sortAscending();
+
+ let identity = MailServices.accounts.getFirstIdentityForServer(
+ win.gDBView.getMsgHdrAt(0).folder.server
+ );
+ identity.archiveGranularity = Ci.nsIMsgIdentity.perYearArchiveFolders;
+ // We need to get all the info about the messages before we do the archive,
+ // because deleting the headers could make extracting values from them fail.
+ let firstMsgHdr = win.gDBView.getMsgHdrAt(0);
+ let lastMsgHdr = win.gDBView.getMsgHdrAt(12);
+ let firstMsgHdrMsgId = firstMsgHdr.messageId;
+ let lastMsgHdrMsgId = lastMsgHdr.messageId;
+ let firstMsgDate = new Date(firstMsgHdr.date / 1000);
+ let firstMsgYear = firstMsgDate.getFullYear().toString();
+ let lastMsgDate = new Date(lastMsgHdr.date / 1000);
+ let lastMsgYear = lastMsgDate.getFullYear().toString();
+
+ win.threadTree.scrollToIndex(0, true);
+ await TestUtils.waitForCondition(
+ () => win.threadTree.getRowAtIndex(0),
+ "Row 0 scrolled into view"
+ );
+ select_click_row(0);
+ win.threadTree.scrollToIndex(12, true);
+ await TestUtils.waitForCondition(
+ () => win.threadTree.getRowAtIndex(12),
+ "Row 12 scrolled into view"
+ );
+ select_control_click_row(12);
+
+ // Press the archive key. The results should go into two separate years.
+ archive_selected_messages();
+
+ // Figure out where the messages should have gone.
+ let archiveRoot = "mailbox://nobody@Local%20Folders/Archives";
+ let firstArchiveUri = archiveRoot + "/" + firstMsgYear;
+ let lastArchiveUri = archiveRoot + "/" + lastMsgYear;
+ if (keep_structure) {
+ firstArchiveUri += "/ArchiveSrc";
+ lastArchiveUri += "/ArchiveSrc";
+ }
+ let firstArchiveFolder = MailUtils.getOrCreateFolder(firstArchiveUri);
+ let lastArchiveFolder = MailUtils.getOrCreateFolder(lastArchiveUri);
+ await be_in_folder(firstArchiveFolder);
+ Assert.ok(
+ win.gDBView.getMsgHdrAt(0).messageId == firstMsgHdrMsgId,
+ "Message should have been archived to " +
+ firstArchiveUri +
+ ", but it isn't present there"
+ );
+ await be_in_folder(lastArchiveFolder);
+
+ Assert.ok(
+ win.gDBView.getMsgHdrAt(0).messageId == lastMsgHdrMsgId,
+ "Message should have been archived to " +
+ lastArchiveUri +
+ ", but it isn't present there"
+ );
+}
+
+add_task(async function test_monthly_archive() {
+ enable_archiving(true);
+ await monthly_archive(false);
+});
+
+async function monthly_archive(keep_structure) {
+ await be_in_folder(archiveSrcFolder);
+
+ let win = get_about_3pane();
+ let identity = MailServices.accounts.getFirstIdentityForServer(
+ win.gDBView.getMsgHdrAt(0).folder.server
+ );
+ identity.archiveGranularity = Ci.nsIMsgIdentity.perMonthArchiveFolders;
+ select_click_row(0);
+ select_control_click_row(1);
+
+ let firstMsgHdr = win.gDBView.getMsgHdrAt(0);
+ let lastMsgHdr = win.gDBView.getMsgHdrAt(1);
+ let firstMsgHdrMsgId = firstMsgHdr.messageId;
+ let lastMsgHdrMsgId = lastMsgHdr.messageId;
+ let firstMsgDate = new Date(firstMsgHdr.date / 1000);
+ let firstMsgYear = firstMsgDate.getFullYear().toString();
+ let firstMonthFolderName =
+ firstMsgYear +
+ "-" +
+ (firstMsgDate.getMonth() + 1).toString().padStart(2, "0");
+ let lastMsgDate = new Date(lastMsgHdr.date / 1000);
+ let lastMsgYear = lastMsgDate.getFullYear().toString();
+ let lastMonthFolderName =
+ lastMsgYear +
+ "-" +
+ (lastMsgDate.getMonth() + 1).toString().padStart(2, "0");
+
+ // Press the archive key. The results should go into two separate months.
+ archive_selected_messages();
+
+ // Figure out where the messages should have gone.
+ let archiveRoot = "mailbox://nobody@Local%20Folders/Archives";
+ let firstArchiveUri =
+ archiveRoot + "/" + firstMsgYear + "/" + firstMonthFolderName;
+ let lastArchiveUri =
+ archiveRoot + "/" + lastMsgYear + "/" + lastMonthFolderName;
+ if (keep_structure) {
+ firstArchiveUri += "/ArchiveSrc";
+ lastArchiveUri += "/ArchiveSrc";
+ }
+ let firstArchiveFolder = MailUtils.getOrCreateFolder(firstArchiveUri);
+ let lastArchiveFolder = MailUtils.getOrCreateFolder(lastArchiveUri);
+ await be_in_folder(firstArchiveFolder);
+ Assert.ok(
+ win.gDBView.getMsgHdrAt(0).messageId == firstMsgHdrMsgId,
+ "Message should have been archived to Local Folders/" +
+ firstMsgYear +
+ "/" +
+ firstMonthFolderName +
+ "/Archives, but it isn't present there"
+ );
+ await be_in_folder(lastArchiveFolder);
+ Assert.ok(
+ win.gDBView.getMsgHdrAt(0).messageId == lastMsgHdrMsgId,
+ "Message should have been archived to Local Folders/" +
+ lastMsgYear +
+ "/" +
+ lastMonthFolderName +
+ "/Archives, but it isn't present there"
+ );
+}
+
+add_task(async function test_folder_structure_archiving() {
+ enable_archiving(true);
+ Services.prefs.setBoolPref(
+ "mail.identity.default.archive_keep_folder_structure",
+ true
+ );
+ await monthly_archive(true);
+ await yearly_archive(true);
+});
+
+add_task(async function test_selection_after_archive() {
+ let win = get_about_3pane();
+ enable_archiving(true);
+ await be_in_folder(archiveSrcFolder);
+ let identity = MailServices.accounts.getFirstIdentityForServer(
+ win.gDBView.getMsgHdrAt(0).folder.server
+ );
+ identity.archiveGranularity = Ci.nsIMsgIdentity.perMonthArchiveFolders;
+ // We had a bug where we would always select the 0th message after an
+ // archive, so test that we'll actually select the next remaining message
+ // by archiving rows 1 & 2 and verifying that the 3rd message gets selected.
+ // let hdrToSelect =
+ select_click_row(3);
+ select_click_row(1);
+ select_control_click_row(2);
+ archive_selected_messages();
+ // assert_selected_and_displayed(hdrToSelect); TODO
+});
+
+add_task(async function test_disabled_archive() {
+ let win = get_about_message();
+ let win3 = get_about_3pane();
+ enable_archiving(false);
+ await be_in_folder(archiveSrcFolder);
+
+ // test single message
+ let current = select_click_row(0);
+ EventUtils.synthesizeKey("a", {});
+ assert_selected_and_displayed(current);
+
+ Assert.ok(
+ win.document.getElementById("hdrArchiveButton").disabled,
+ "Archive button should be disabled when archiving is disabled!"
+ );
+
+ // test message summaries
+ select_click_row(0);
+ current = select_shift_click_row(2);
+ EventUtils.synthesizeKey("a", {});
+ assert_selected_and_displayed(current);
+
+ let htmlframe = win3.multiMessageBrowser;
+ let archiveBtn = htmlframe.contentDocument.getElementById("hdrArchiveButton");
+ Assert.ok(
+ archiveBtn.collapsed,
+ "Multi-message archive button should be disabled when " +
+ "archiving is disabled!"
+ );
+
+ // test message summaries with "large" selection
+ mc.window.gFolderDisplay.MAX_COUNT_FOR_CAN_ARCHIVE_CHECK = 1;
+ select_click_row(0);
+ current = select_shift_click_row(2);
+ EventUtils.synthesizeKey("a", {});
+ assert_selected_and_displayed(current);
+ mc.window.gFolderDisplay.MAX_COUNT_FOR_CAN_ARCHIVE_CHECK = 100;
+
+ htmlframe = mc.window.document.getElementById("multimessage");
+ archiveBtn = htmlframe.contentDocument.getElementById("hdrArchiveButton");
+ Assert.ok(
+ archiveBtn.collapsed,
+ "Multi-message archive button should be disabled when " +
+ "archiving is disabled!"
+ );
+}).skip();
+
+function check_tag_in_message(message, tag, isSet) {
+ let tagSet = message
+ .getStringProperty("keywords")
+ .split(" ")
+ .includes(tag.key);
+ if (isSet) {
+ Assert.ok(tagSet, "Tag '" + tag.name + "' expected on message!");
+ } else {
+ Assert.ok(!tagSet, "Tag '" + tag.name + "' not expected on message!");
+ }
+}
+
+add_task(async function test_tag_keys() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ EventUtils.synthesizeKey("1", {});
+ check_tag_in_message(curMessage, tagArray[0], true);
+
+ EventUtils.synthesizeKey("2", {});
+ check_tag_in_message(curMessage, tagArray[0], true);
+ check_tag_in_message(curMessage, tagArray[1], true);
+
+ EventUtils.synthesizeKey("0", {});
+ check_tag_in_message(curMessage, tagArray[0], false);
+ check_tag_in_message(curMessage, tagArray[1], false);
+}).skip(); // TODO: not working
+
+add_task(async function test_tag_keys_disabled_in_content_tab() {
+ await be_in_folder(unreadFolder);
+ let curMessage = select_click_row(0);
+
+ mc.window.openAddonsMgr("addons://list/theme");
+ await new Promise(resolve => setTimeout(resolve));
+
+ let tab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ wait_for_content_tab_load(tab, "about:addons", 15000);
+
+ // Make sure pressing the "1" key in a content tab doesn't tag a message
+ check_tag_in_message(curMessage, tagArray[0], false);
+ EventUtils.synthesizeKey("1", {});
+ check_tag_in_message(curMessage, tagArray[0], false);
+
+ mc.window.document.getElementById("tabmail").closeTab(tab);
+}).skip(); // TODO: not working
+
+registerCleanupFunction(function () {
+ // Make sure archiving is enabled at the end
+ enable_archiving(true);
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", gAutoRead);
+});
diff --git a/comm/mail/test/browser/folder-display/browser_messageCommandsOnMsgstore.js b/comm/mail/test/browser/folder-display/browser_messageCommandsOnMsgstore.js
new file mode 100644
index 0000000000..899a211de8
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_messageCommandsOnMsgstore.js
@@ -0,0 +1,333 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This tests some commands on messages via the UI. But we specifically check,
+ * whether the commands have an effect in the message store on disk, i.e. the
+ * markings on the messages are stored in the msgStore, not only in the database.
+ * For now, it checks for bug 840418.
+ */
+
+"use strict";
+
+var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");
+const {
+ open_compose_with_forward,
+ open_compose_with_reply,
+ setup_msg_contents,
+} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
+const {
+ be_in_folder,
+ create_folder,
+ empty_folder,
+ get_special_folder,
+ make_message_sets_in_folders,
+ mc,
+ press_delete,
+ right_click_on_row,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+const {
+ click_menus_in_sequence,
+ plan_for_window_close,
+ wait_for_window_close,
+} = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+let gInbox;
+let gOutbox;
+let gAutoRead;
+
+add_setup(async function () {
+ gAutoRead = Services.prefs.getBoolPref("mailnews.mark_message_read.auto");
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", false);
+
+ gOutbox = await get_special_folder(Ci.nsMsgFolderFlags.Queue);
+ gInbox = await create_folder("MsgStoreChecks");
+ await make_message_sets_in_folders([gInbox], [{ count: 6 }]);
+
+ // We delete the first message so that we have to compact anything.
+ await be_in_folder(gInbox);
+ let curMessage = select_click_row(0);
+ press_delete(mc);
+ Assert.notEqual(curMessage, select_click_row(0));
+
+ let urlListener = {
+ compactDone: false,
+
+ OnStartRunningUrl(aUrl) {},
+ OnStopRunningUrl(aUrl, aExitCode) {
+ Assert.equal(aExitCode, 0);
+ Assert.ok(gInbox.msgDatabase.summaryValid);
+ this.compactDone = true;
+ },
+ };
+
+ // Compaction adds the X-Mozilla-Status rows into the messages
+ // that we will need later on.
+ Assert.ok(gInbox.msgStore.supportsCompaction);
+ gInbox.compact(urlListener, null);
+
+ utils.waitFor(
+ function () {
+ return urlListener.compactDone;
+ },
+ "Timeout waiting for compact to complete",
+ 10000,
+ 100
+ );
+});
+
+/**
+ * Checks that a message has particular status stored in the mbox file,
+ * in the X-Mozilla-Status header.
+ *
+ * @param folder The folder containing the message to check.
+ * @param offset Offset to the start of the message within mbox file.
+ * @param expectedStatus The required status of the message.
+ */
+async function check_status(folder, offset, expectedStatus) {
+ let mboxstring = await IOUtils.readUTF8(folder.filePath.path);
+
+ // Ah-hoc header parsing. Only check the first 1KB because the X-Mozilla-*
+ // headers should be near the start.
+ let msg = mboxstring.slice(offset, offset + 1024);
+ msg = msg.replace(/\r/g, ""); // Simplify by using LFs only.
+ for (let line of msg.split("\n")) {
+ if (line == "") {
+ break; // end of header block.
+ }
+ if (line.startsWith("X-Mozilla-Status:")) {
+ let hexValue = /:\s*([0-9a-f]+)/i.exec(line)[1];
+ let gotStatus = parseInt(hexValue, 16);
+ Assert.equal(
+ gotStatus,
+ expectedStatus,
+ `Check X-Mozilla-Status (for msg at offset ${offset})`
+ );
+ return;
+ }
+ }
+ // If we got this far, we didn't find the header.
+ Assert.ok(
+ false,
+ `Find X-Mozilla-Status header (for msg at offset ${offset})`
+ );
+}
+
+add_task(async function test_mark_messages_read() {
+ be_in_folder(gOutbox); // TODO shouldn't have to swap folders
+ // 5 messages in the folder
+ await be_in_folder(gInbox);
+ let curMessage = select_click_row(0);
+ // Store the offset because it will be unavailable via the hdr
+ // after the message is deleted.
+ let offset = curMessage.messageOffset;
+ await check_status(gInbox, offset, 0); // status = unread
+ press_delete(mc);
+ Assert.notEqual(curMessage, select_click_row(0));
+ await check_status(
+ gInbox,
+ offset,
+ Ci.nsMsgMessageFlags.Read + Ci.nsMsgMessageFlags.Expunged
+ );
+
+ // 4 messages in the folder.
+ curMessage = select_click_row(0);
+ await check_status(gInbox, curMessage.messageOffset, 0); // status = unread
+
+ // Make sure we can mark all read with >0 messages unread.
+ await right_click_on_row(0);
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ await click_menus_in_sequence(getMailContext(), [
+ { id: "mailContext-mark" },
+ { id: "mailContext-markAllRead" },
+ ]);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ // All the 4 messages should now be read.
+ Assert.ok(curMessage.isRead, "Message should have been marked Read!");
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Read
+ );
+ curMessage = select_click_row(1);
+ Assert.ok(curMessage.isRead, "Message should have been marked Read!");
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Read
+ );
+ curMessage = select_click_row(2);
+ Assert.ok(curMessage.isRead, "Message should have been marked Read!");
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Read
+ );
+ curMessage = select_click_row(3);
+ Assert.ok(curMessage.isRead, "Message should have been marked Read!");
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Read
+ );
+
+ // Let's have the last message unread.
+ await right_click_on_row(3);
+ hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ await click_menus_in_sequence(getMailContext(), [
+ { id: "mailContext-mark" },
+ { id: "mailContext-markUnread" },
+ ]);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ Assert.ok(!curMessage.isRead, "Message should have not been marked Read!");
+ await check_status(gInbox, curMessage.messageOffset, 0);
+});
+
+add_task(async function test_mark_messages_flagged() {
+ // Mark a message with the star.
+ let curMessage = select_click_row(1);
+ await right_click_on_row(1);
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ await click_menus_in_sequence(getMailContext(), [
+ { id: "mailContext-mark" },
+ { id: "mailContext-markFlagged" },
+ ]);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ Assert.ok(curMessage.isFlagged, "Message should have been marked Flagged!");
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Read + Ci.nsMsgMessageFlags.Marked
+ );
+});
+
+async function subtest_check_queued_message() {
+ // Always check the last message in the Outbox for the correct flag.
+ await be_in_folder(gOutbox);
+ let lastMsg = [...gOutbox.messages].pop();
+ await check_status(
+ gOutbox,
+ lastMsg.messageOffset,
+ Ci.nsMsgMessageFlags.Queued
+ );
+}
+
+/**
+ * Create a reply or forward of a message and queue it for sending later.
+ *
+ * @param aMsgRow Row index of message in Inbox that is to be replied/forwarded.
+ * @param aReply true = reply, false = forward.
+ */
+async function reply_forward_message(aMsgRow, aReply) {
+ await be_in_folder(gInbox);
+ select_click_row(aMsgRow);
+ let cwc;
+ if (aReply) {
+ // Reply to the message.
+ cwc = open_compose_with_reply();
+ } else {
+ // Forward the message.
+ cwc = open_compose_with_forward();
+ // Type in some recipient.
+ setup_msg_contents(cwc, "somewhere@host.invalid", "", "");
+ }
+
+ // Send it later.
+ plan_for_window_close(cwc);
+ // Ctrl+Shift+Return = Send Later
+ cwc.window.document.getElementById("messageEditor").focus();
+ EventUtils.synthesizeKey(
+ "VK_RETURN",
+ {
+ shiftKey: true,
+ accelKey: true,
+ },
+ cwc.window
+ );
+ wait_for_window_close(cwc);
+
+ await subtest_check_queued_message();
+
+ // Now this is hacky. We can't get the message to be sent out of TB because there
+ // is no fake SMTP server support yet.
+ // But we know that upon real sending of the message, the code would/should call
+ // .addMessageDispositionState(). So call it directly and check the expected
+ // flags were set. This is risky as the real code could change and call
+ // a different function and the purpose of this test would be lost.
+ await be_in_folder(gInbox);
+ let curMessage = select_click_row(aMsgRow);
+ let disposition = aReply
+ ? gInbox.nsMsgDispositionState_Replied
+ : gInbox.nsMsgDispositionState_Forwarded;
+ gInbox.addMessageDispositionState(curMessage, disposition);
+}
+
+add_task(async function test_mark_messages_replied() {
+ await reply_forward_message(2, true);
+ let curMessage = select_click_row(2);
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Replied + Ci.nsMsgMessageFlags.Read
+ );
+});
+
+add_task(async function test_mark_messages_forwarded() {
+ await be_in_folder(gInbox);
+ // Forward a clean message.
+ await reply_forward_message(3, false);
+ let curMessage = select_click_row(3);
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Forwarded
+ );
+
+ // Forward a message that is read and already replied to.
+ curMessage = select_click_row(2);
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Replied + Ci.nsMsgMessageFlags.Read
+ );
+ await reply_forward_message(2, false);
+ await check_status(
+ gInbox,
+ curMessage.messageOffset,
+ Ci.nsMsgMessageFlags.Forwarded +
+ Ci.nsMsgMessageFlags.Replied +
+ Ci.nsMsgMessageFlags.Read
+ );
+});
+
+registerCleanupFunction(async function () {
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", gAutoRead);
+ // Clear all the created messages.
+ await be_in_folder(gInbox.parent);
+ await empty_folder(gInbox);
+ // await empty_folder(gOutbox); TODO
+ gInbox.server.rootFolder.emptyTrash(null);
+});
diff --git a/comm/mail/test/browser/folder-display/browser_messagePaneVisibility.js b/comm/mail/test/browser/folder-display/browser_messagePaneVisibility.js
new file mode 100644
index 0000000000..1cab2740e0
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_messagePaneVisibility.js
@@ -0,0 +1,250 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that the message pane collapses properly, stays collapsed amongst tab
+ * changes, and that persistence works (to a first approximation).
+ */
+
+"use strict";
+
+var {
+ assert_message_pane_hidden,
+ assert_message_pane_visible,
+ be_in_folder,
+ close_tab,
+ create_folder,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_tab,
+ open_selected_message_in_new_tab,
+ select_click_row,
+ switch_tab,
+ toggle_message_pane,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder;
+
+add_setup(async function () {
+ folder = await create_folder("MessagePaneVisibility");
+ await make_message_sets_in_folders([folder], [{ count: 3 }]);
+});
+
+/**
+ * By default, the message pane should be visible. Make sure that this state of
+ * affairs is correct in terms of menu options, splitters, etc.
+ */
+add_task(async function test_message_pane_visible_state_is_right() {
+ await be_in_folder(folder);
+ assert_message_pane_visible();
+ Assert.ok(true, "test_message_pane_visible_state_is_right ran to completion");
+});
+
+/**
+ * Toggle the message off.
+ */
+add_task(function test_toggle_message_pane_off() {
+ toggle_message_pane();
+ assert_message_pane_hidden();
+ Assert.ok(true, "test_toggle_message_pane_off ran to completion");
+});
+
+/**
+ * Toggle the message pane on.
+ */
+add_task(function test_toggle_message_pane_on() {
+ toggle_message_pane();
+ assert_message_pane_visible();
+ Assert.ok(true, "test_toggle_message_pane_on ran to completion");
+});
+
+/**
+ * Make sure that the message tab isn't broken by being invoked from a folder tab
+ * with a collapsed message pane.
+ */
+add_task(
+ async function test_collapsed_message_pane_does_not_break_message_tab() {
+ await be_in_folder(folder);
+
+ // - toggle message pane off
+ toggle_message_pane();
+ assert_message_pane_hidden();
+
+ // - open message tab, make sure the message pane is visible
+ select_click_row(0);
+ let tabMessage = await open_selected_message_in_new_tab();
+
+ // - close the tab, sanity check the transition was okay
+ close_tab(tabMessage);
+ assert_message_pane_hidden();
+
+ // - restore the state...
+ toggle_message_pane();
+
+ Assert.ok(
+ true,
+ "test_collapsed_message_pane_does_not_break_message_tab ran to completion"
+ );
+ }
+);
+
+/**
+ * Make sure that switching to message tabs or folder pane tabs with a different
+ * message pane state does not break. This test should cover all transition
+ * states.
+ */
+add_task(async function test_message_pane_is_sticky() {
+ let tabFolderA = await be_in_folder(folder);
+ assert_message_pane_visible();
+
+ // [folder+ => (new) message]
+ select_click_row(0);
+ let tabMessage = await open_selected_message_in_new_tab();
+
+ // [message => folder+]
+ await switch_tab(tabFolderA);
+ assert_message_pane_visible();
+
+ // [folder+ => (new) folder+]
+ let tabFolderB = await open_folder_in_new_tab(folder);
+ assert_message_pane_visible();
+
+ // [folder pane toggle + => -]
+ toggle_message_pane();
+ assert_message_pane_hidden();
+
+ // [folder- => folder+]
+ await switch_tab(tabFolderA);
+ assert_message_pane_visible();
+
+ // (redundant) [ folder pane toggle + => -]
+ toggle_message_pane();
+ assert_message_pane_hidden();
+
+ // [folder- => message]
+ await switch_tab(tabMessage);
+
+ // [message => folder-]
+ close_tab(tabMessage);
+ assert_message_pane_hidden();
+
+ // [folder- => (new) folder-]
+ // (we are testing inheritance here)
+ let tabFolderC = await open_folder_in_new_tab(folder);
+ assert_message_pane_hidden();
+
+ // [folder- => folder-]
+ close_tab(tabFolderC);
+ // the tab we are on now doesn't matter, so we don't care
+ assert_message_pane_hidden();
+ await switch_tab(tabFolderB);
+
+ // [ folder pane toggle - => + ]
+ toggle_message_pane();
+ assert_message_pane_visible();
+
+ // [folder+ => folder-]
+ close_tab(tabFolderB);
+ assert_message_pane_hidden();
+
+ // (redundant) [ folder pane toggle - => + ]
+ toggle_message_pane();
+ assert_message_pane_visible();
+
+ Assert.ok(true, "test_message_pane_is_sticky ran to completion");
+});
+
+/**
+ * Test that if we serialize and restore the tabs that the message pane is in
+ * the expected collapsed/non-collapsed state. Because of the special "first
+ * tab" situation, we need to do this twice to test each case for the first
+ * tab. For additional thoroughness we also flip the state we have the other
+ * tabs be in.
+ */
+add_task(async function test_message_pane_persistence_generally_works() {
+ await be_in_folder(folder);
+
+ let tabmail = mc.window.document.getElementById("tabmail");
+
+ // helper to open tabs with the folder pane in the desired states (1 for
+ // visible, 0 for hidden)
+ async function openTabs(aConfig) {
+ for (let [iTab, messagePaneVisible] of aConfig.entries()) {
+ if (iTab != 0) {
+ await open_folder_in_new_tab(folder);
+ }
+ if (
+ tabmail.currentAbout3Pane.paneLayout.messagePaneVisible !=
+ messagePaneVisible
+ ) {
+ toggle_message_pane();
+ }
+ }
+ }
+
+ // close everything but the first tab.
+ function closeTabs() {
+ while (tabmail.tabInfo.length > 1) {
+ close_tab(1);
+ }
+ }
+
+ async function verifyTabs(aConfig) {
+ for (let [iTab, messagePaneVisible] of aConfig.entries()) {
+ info("tab " + iTab);
+
+ await switch_tab(iTab);
+ if (tabmail.currentAbout3Pane.document.readyState != "complete") {
+ await BrowserTestUtils.waitForEvent(tabmail.currentAbout3Pane, "load");
+ await new Promise(resolve =>
+ tabmail.currentAbout3Pane.setTimeout(resolve)
+ );
+ }
+
+ if (messagePaneVisible) {
+ assert_message_pane_visible();
+ } else {
+ assert_message_pane_hidden();
+ }
+ }
+ }
+
+ let configs = [
+ // 1st time: [+ - - + +]
+ [1, 0, 0, 1, 1],
+ // 2nd time: [- + + - -]
+ [0, 1, 1, 0, 0],
+ ];
+ for (let config of configs) {
+ await openTabs(config);
+ await verifyTabs(config); // make sure openTabs did its job right
+ let state = tabmail.persistTabs();
+ closeTabs();
+
+ Assert.equal(state.tabs[0].state.messagePaneVisible, config[0]);
+ Assert.equal(state.tabs[1].state.messagePaneVisible, config[1]);
+ Assert.equal(state.tabs[2].state.messagePaneVisible, config[2]);
+ Assert.equal(state.tabs[3].state.messagePaneVisible, config[3]);
+ Assert.equal(state.tabs[4].state.messagePaneVisible, config[4]);
+
+ // toggle the state for the current tab so we can be sure that it knows how
+ // to change things.
+ toggle_message_pane();
+
+ tabmail.restoreTabs(state);
+ await verifyTabs(config);
+ closeTabs();
+
+ // toggle the first tab again. This sets - properly for the second pass and
+ // restores it to + for when we are done.
+ toggle_message_pane();
+ }
+
+ Assert.ok(
+ true,
+ "test_message_pane_persistence_generally_works ran to completion"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_messageReloads.js b/comm/mail/test/browser/folder-display/browser_messageReloads.js
new file mode 100644
index 0000000000..e89793d865
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_messageReloads.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that message reloads happen properly when the message pane is hidden,
+ * and then made visible again.
+ */
+
+"use strict";
+
+var {
+ assert_message_pane_hidden,
+ assert_message_pane_visible,
+ assert_selected_and_displayed,
+ be_in_folder,
+ close_tab,
+ make_message_sets_in_folders,
+ create_folder,
+ open_folder_in_new_tab,
+ select_click_row,
+ switch_tab,
+ toggle_message_pane,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder;
+
+add_setup(async function () {
+ folder = await create_folder("MessageReloads");
+ await make_message_sets_in_folders([folder], [{ count: 1 }]);
+});
+
+add_task(async function test_message_reloads_work_with_message_pane_toggles() {
+ await be_in_folder(folder);
+
+ assert_message_pane_visible();
+ select_click_row(0);
+ // Toggle the message pane off, then on
+ toggle_message_pane();
+ assert_message_pane_hidden();
+ toggle_message_pane();
+ assert_message_pane_visible();
+ // Open a new tab with the same message
+ let tab = await open_folder_in_new_tab(folder);
+ // Toggle the message pane off
+ assert_message_pane_visible();
+ toggle_message_pane();
+ assert_message_pane_hidden();
+ // Go back to the first tab, and make sure the message is actually displayed
+ await switch_tab(0);
+ assert_message_pane_visible();
+ assert_selected_and_displayed(0);
+
+ close_tab(tab);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_messageSize.js b/comm/mail/test/browser/folder-display/browser_messageSize.js
new file mode 100644
index 0000000000..d62c3d808c
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_messageSize.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that the size column of in the message list is formatted properly (e.g.
+ 0.1 KB, 1.2 KB, 12.3 KB, 123 KB, and likewise for MB and GB).
+ */
+
+"use strict";
+
+var {
+ add_message_to_folder,
+ be_in_folder,
+ create_folder,
+ create_message,
+ get_about_3pane,
+ mc,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder;
+
+add_setup(async function () {
+ folder = await create_folder("MessageSizeA");
+
+ // Create messages with sizes in the byte, KB, and MB ranges.
+ let bytemsg = create_message({ body: { body: " " } });
+
+ let kbstring = "x ".repeat(1024 / 2);
+ let kbmsg = create_message({ body: { body: kbstring } });
+
+ let mbstring = kbstring.repeat(1024);
+ let mbmsg = create_message({ body: { body: mbstring } });
+
+ await add_message_to_folder([folder], bytemsg);
+ await add_message_to_folder([folder], kbmsg);
+ await add_message_to_folder([folder], mbmsg);
+});
+
+async function _help_test_message_size(index, unit) {
+ await be_in_folder(folder);
+
+ // Select the nth message
+ let curMessage = select_click_row(index);
+ // Look at the size column's data
+ let sizeStr = get_about_3pane().gDBView.cellTextForColumn(index, "sizeCol");
+
+ // Note: this assumes that the numeric part of the size string is first
+ let realSize = curMessage.messageSize;
+ let abbrSize = parseFloat(sizeStr);
+
+ if (isNaN(abbrSize)) {
+ throw new Error("formatted size is not numeric: '" + sizeStr + "'");
+ }
+ if (Math.abs(realSize / Math.pow(1024, unit) - abbrSize) > 0.5) {
+ throw new Error("size mismatch: '" + realSize + "' and '" + sizeStr + "'");
+ }
+}
+
+add_task(async function test_byte_message_size() {
+ await _help_test_message_size(0, 1);
+});
+
+add_task(async function test_kb_message_size() {
+ await _help_test_message_size(1, 1);
+});
+
+add_task(async function test_mb_message_size() {
+ await _help_test_message_size(2, 2);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_messageWindow.js b/comm/mail/test/browser/folder-display/browser_messageWindow.js
new file mode 100644
index 0000000000..ac8d900195
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_messageWindow.js
@@ -0,0 +1,153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that we can open and close a standalone message display window from the
+ * folder pane.
+ */
+
+"use strict";
+
+var {
+ add_message_sets_to_folders,
+ assert_selected_and_displayed,
+ be_in_folder,
+ create_folder,
+ create_thread,
+ open_selected_message_in_new_window,
+ plan_for_message_display,
+ press_delete,
+ select_click_row,
+ wait_for_message_display_completion,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { plan_for_window_close, wait_for_window_close } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+var folderA, folderB;
+var curMessage;
+
+add_setup(async function () {
+ folderA = await create_folder("MessageWindowA");
+ folderB = await create_folder("MessageWindowB");
+ // create three messages in the folder to display
+ let msg1 = create_thread(1);
+ let msg2 = create_thread(1);
+ let thread1 = create_thread(2);
+ let thread2 = create_thread(2);
+ await add_message_sets_to_folders([folderA], [msg1, msg2, thread1, thread2]);
+ // add two more messages in another folder
+ let msg3 = create_thread(1);
+ let msg4 = create_thread(1);
+ await add_message_sets_to_folders([folderB], [msg3, msg4]);
+ folderA.msgDatabase.dBFolderInfo.viewFlags =
+ Ci.nsMsgViewFlagsType.kThreadedDisplay;
+});
+
+/** The message window controller. */
+var msgc;
+
+add_task(async function test_open_message_window() {
+ await be_in_folder(folderA);
+
+ // select the first message
+ curMessage = select_click_row(0);
+
+ // display it
+ msgc = await open_selected_message_in_new_window();
+ assert_selected_and_displayed(msgc, curMessage);
+});
+
+/**
+ * Use the "m" keyboard accelerator to mark a message as read or unread.
+ */
+add_task(function test_toggle_read() {
+ curMessage.markRead(false);
+ EventUtils.synthesizeKey("m", {}, msgc.window);
+ Assert.ok(curMessage.isRead, "Message should have been marked read!");
+
+ EventUtils.synthesizeKey("m", {}, msgc.window);
+ Assert.ok(!curMessage.isRead, "Message should have been marked unread!");
+});
+
+/**
+ * Use the "f" keyboard accelerator to navigate to the next message,
+ * and verify that it is indeed loaded.
+ */
+add_task(function test_navigate_to_next_message() {
+ plan_for_message_display(msgc);
+ EventUtils.synthesizeKey("f", {}, msgc.window);
+ wait_for_message_display_completion(msgc, true);
+ assert_selected_and_displayed(msgc, 1);
+}).skip();
+
+/**
+ * Delete a single message and verify the next message is loaded. This sets
+ * us up for the next test, which is delete on a collapsed thread after
+ * the previous message was deleted.
+ */
+add_task(function test_delete_single_message() {
+ plan_for_message_display(msgc);
+ press_delete(msgc);
+ wait_for_message_display_completion(msgc, true);
+ assert_selected_and_displayed(msgc, 1);
+}).skip();
+
+/**
+ * Delete the current message, and verify that it only deletes
+ * a single message, not the messages in the collapsed thread
+ */
+add_task(function test_del_collapsed_thread() {
+ plan_for_message_display(msgc);
+ press_delete(msgc);
+ if (folderA.getTotalMessages(false) != 4) {
+ throw new Error("should have only deleted one message");
+ }
+ wait_for_message_display_completion(msgc, true);
+ assert_selected_and_displayed(msgc, 1);
+}).skip();
+
+/**
+ * Hit n enough times to mark all messages in folder A read, and then accept the
+ * modal dialog saying that we should move to the next folder. Then, assert that
+ * the message displayed in the standalone message window is folder B's first
+ * message (since all messages in folder B were unread).
+ */
+add_task(async function test_next_unread() {
+ for (let i = 0; i < 3; ++i) {
+ plan_for_message_display(msgc);
+ EventUtils.synthesizeKey("n", {}, msgc.window);
+ wait_for_message_display_completion(msgc, true);
+ }
+
+ for (let m of folderA.messages) {
+ Assert.ok(m.isRead, `${m.messageId} is read`);
+ }
+
+ let dialogPromise = BrowserTestUtils.promiseAlertDialog("accept");
+ EventUtils.synthesizeKey("n", {}, msgc.window);
+ plan_for_message_display(msgc);
+ await dialogPromise;
+ wait_for_message_display_completion(msgc, true);
+
+ // move to folder B
+ await be_in_folder(folderB);
+
+ // select the first message, and make sure it's not read
+ let msg = select_click_row(0);
+
+ // make sure we've been displaying the right message
+ assert_selected_and_displayed(msgc, msg);
+}).skip();
+
+/**
+ * Close the window by hitting escape.
+ */
+add_task(function test_close_message_window() {
+ plan_for_window_close(msgc);
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, msgc.window);
+ wait_for_window_close(msgc);
+});
diff --git a/comm/mail/test/browser/folder-display/browser_openingMessages.js b/comm/mail/test/browser/folder-display/browser_openingMessages.js
new file mode 100644
index 0000000000..ca898e6e10
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_openingMessages.js
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that we open single and multiple messages from the thread pane
+ * according to the mail.openMessageBehavior preference, and that we have the
+ * correct message headers displayed in whatever we open.
+ *
+ * Currently tested:
+ * - opening single and multiple messages in tabs
+ * - opening a single message in a window. (Multiple messages require a fair
+ * amount of additional work and are hard to test. We're also assuming here
+ * that multiple messages opened in windows are just the same function called
+ * repeatedly.)
+ * - reusing an existing window to show another message
+ */
+
+"use strict";
+
+var {
+ assert_message_pane_focused,
+ assert_number_of_tabs_open,
+ assert_selected_and_displayed,
+ assert_tab_mode_name,
+ assert_tab_titled_from,
+ be_in_folder,
+ close_message_window,
+ close_tab,
+ create_folder,
+ make_message_sets_in_folders,
+ mc,
+ open_selected_message,
+ open_selected_messages,
+ plan_for_message_display,
+ reset_open_message_behavior,
+ select_click_row,
+ select_shift_click_row,
+ set_open_message_behavior,
+ switch_tab,
+ wait_for_message_display_completion,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { async_plan_for_new_window, wait_for_new_window } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+// One folder's enough
+var folder = null;
+
+// Number of messages to open for multi-message tests
+var NUM_MESSAGES_TO_OPEN = 5;
+
+add_setup(async function () {
+ folder = await create_folder("OpeningMessagesA");
+ await make_message_sets_in_folders([folder], [{ count: 10 }]);
+});
+
+/**
+ * Test opening a single message in a new tab.
+ */
+add_task(async function test_open_single_message_in_tab() {
+ set_open_message_behavior("NEW_TAB");
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ let preCount =
+ mc.window.document.getElementById("tabmail").tabContainer.allTabs.length;
+ await be_in_folder(folder);
+ // Select one message
+ let msgHdr = select_click_row(1);
+ // Open it
+ open_selected_message();
+ // Check that the tab count has increased by 1
+ assert_number_of_tabs_open(preCount + 1);
+ // Check that the currently displayed tab is a message tab (i.e. our newly
+ // opened tab is in the foreground)
+ assert_tab_mode_name(null, "mailMessageTab");
+
+ let tab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ if (
+ tab.chromeBrowser.docShell.isLoadingDocument ||
+ tab.chromeBrowser.currentURI.spec != "about:message"
+ ) {
+ await BrowserTestUtils.browserLoaded(tab.chromeBrowser);
+ }
+
+ // Check that the message header displayed is the right one
+ assert_selected_and_displayed(msgHdr);
+ // Check that the message pane is focused
+ assert_message_pane_focused();
+ // Clean up, close the tab
+ close_tab(mc.window.document.getElementById("tabmail").currentTabInfo);
+ await switch_tab(folderTab);
+ reset_open_message_behavior();
+});
+
+/**
+ * Test opening multiple messages in new tabs.
+ */
+add_task(async function test_open_multiple_messages_in_tabs() {
+ set_open_message_behavior("NEW_TAB");
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ let preCount =
+ mc.window.document.getElementById("tabmail").tabContainer.allTabs.length;
+ await be_in_folder(folder);
+
+ // Select a bunch of messages
+ select_click_row(1);
+ let selectedMessages = select_shift_click_row(NUM_MESSAGES_TO_OPEN);
+ // Open them
+ open_selected_messages();
+ // Check that the tab count has increased by the correct number
+ assert_number_of_tabs_open(preCount + NUM_MESSAGES_TO_OPEN);
+ // Check that the currently displayed tab is a message tab (i.e. one of our
+ // newly opened tabs is in the foreground)
+ assert_tab_mode_name(null, "mailMessageTab");
+
+ // Now check whether each of the NUM_MESSAGES_TO_OPEN tabs has the correct
+ // title
+ for (let i = 0; i < NUM_MESSAGES_TO_OPEN; i++) {
+ assert_tab_titled_from(
+ mc.window.document.getElementById("tabmail").tabInfo[preCount + i],
+ selectedMessages[i]
+ );
+ }
+
+ // Check whether each tab has the correct message and whether the message pane
+ // is focused in each case, then close it to load the previous tab.
+ for (let i = 0; i < NUM_MESSAGES_TO_OPEN; i++) {
+ assert_selected_and_displayed(selectedMessages.pop());
+ assert_message_pane_focused();
+ close_tab(mc.window.document.getElementById("tabmail").currentTabInfo);
+ }
+ await switch_tab(folderTab);
+ reset_open_message_behavior();
+});
+
+/**
+ * Test opening a message in a new window.
+ */
+add_task(async function test_open_message_in_new_window() {
+ set_open_message_behavior("NEW_WINDOW");
+ await be_in_folder(folder);
+
+ // Select a message
+ let msgHdr = select_click_row(1);
+
+ let newWindowPromise = async_plan_for_new_window("mail:messageWindow");
+ // Open it
+ open_selected_message();
+ let msgc = await newWindowPromise;
+ wait_for_message_display_completion(msgc, true);
+
+ assert_selected_and_displayed(msgc, msgHdr);
+
+ // Clean up, close the window
+ close_message_window(msgc);
+ reset_open_message_behavior();
+});
+
+/**
+ * Test reusing an existing window to open a new message.
+ */
+add_task(async function test_open_message_in_existing_window() {
+ set_open_message_behavior("EXISTING_WINDOW");
+ await be_in_folder(folder);
+
+ // Open up a window
+ select_click_row(1);
+ let newWindowPromise = async_plan_for_new_window("mail:messageWindow");
+ open_selected_message();
+ let msgc = await newWindowPromise;
+ wait_for_message_display_completion(msgc, true);
+
+ // Select another message and open it
+ let msgHdr = select_click_row(2);
+ plan_for_message_display(msgc);
+ open_selected_message();
+ wait_for_message_display_completion(msgc, true);
+
+ // Check if our old window displays the message
+ assert_selected_and_displayed(msgc, msgHdr);
+ // Clean up, close the window
+ close_message_window(msgc);
+ reset_open_message_behavior();
+});
diff --git a/comm/mail/test/browser/folder-display/browser_openingMessagesWithoutABackingView.js b/comm/mail/test/browser/folder-display/browser_openingMessagesWithoutABackingView.js
new file mode 100644
index 0000000000..d5459b8692
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_openingMessagesWithoutABackingView.js
@@ -0,0 +1,248 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that messages without a backing view are opened correctly. Examples of
+ * messages without a backing view are those opened from the command line or
+ * desktop search integration results.
+ */
+
+"use strict";
+
+var {
+ add_to_toolbar,
+ assert_message_pane_focused,
+ assert_messages_not_in_view,
+ assert_number_of_tabs_open,
+ assert_selected_and_displayed,
+ assert_tab_mode_name,
+ assert_tab_titled_from,
+ be_in_folder,
+ close_message_window,
+ close_tab,
+ create_folder,
+ get_about_3pane,
+ make_message_sets_in_folders,
+ mc,
+ plan_for_message_display,
+ remove_from_toolbar,
+ reset_open_message_behavior,
+ set_mail_view,
+ set_open_message_behavior,
+ switch_tab,
+ wait_for_message_display_completion,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { async_plan_for_new_window, wait_for_new_window } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { MailViewConstants } = ChromeUtils.import(
+ "resource:///modules/MailViewManager.jsm"
+);
+
+// One folder's enough
+var folder = null;
+
+// A list of the message headers in this folder
+var msgHdrsInFolder = null;
+
+// Number of messages to open for multi-message tests
+var NUM_MESSAGES_TO_OPEN = 5;
+
+add_setup(async function () {
+ folder = await create_folder("OpeningMessagesNoBackingViewA");
+ await make_message_sets_in_folders([folder], [{ count: 10 }]);
+});
+
+/**
+ * Test opening a single message without a backing view in a new tab.
+ */
+async function test_open_single_message_without_backing_view_in_tab() {
+ set_open_message_behavior("NEW_TAB");
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ let preCount =
+ mc.window.document.getElementById("tabmail").tabContainer.allTabs.length;
+ await be_in_folder(folder);
+
+ let win = get_about_3pane();
+
+ if (!msgHdrsInFolder) {
+ msgHdrsInFolder = [];
+ // Make a list of all the message headers in this folder
+ for (let i = 0; i < 10; i++) {
+ msgHdrsInFolder.push(win.gDBView.getMsgHdrAt(i));
+ }
+ }
+ // Get a reference to a header
+ let msgHdr = msgHdrsInFolder[4];
+ // Open it
+ MailUtils.displayMessage(msgHdr);
+ // This is going to trigger a message display in the main 3pane window. Since
+ // the message will open in a new tab, we shouldn't
+ // plan_for_message_display().
+ wait_for_message_display_completion(mc, true);
+ // Check that the tab count has increased by 1
+ assert_number_of_tabs_open(preCount + 1);
+ // Check that the currently displayed tab is a message tab (i.e. our newly
+ // opened tab is in the foreground)
+ assert_tab_mode_name(null, "mailMessageTab");
+ // Check that the message header displayed is the right one
+ assert_selected_and_displayed(msgHdr);
+ // Check that the message pane is focused
+ assert_message_pane_focused();
+ // Clean up, close the tab
+ close_tab(mc.window.document.getElementById("tabmail").currentTabInfo);
+ await switch_tab(folderTab);
+ reset_open_message_behavior();
+}
+add_task(test_open_single_message_without_backing_view_in_tab);
+
+/**
+ * Test opening multiple messages without backing views in new tabs.
+ */
+async function test_open_multiple_messages_without_backing_views_in_tabs() {
+ set_open_message_behavior("NEW_TAB");
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ let preCount =
+ mc.window.document.getElementById("tabmail").tabContainer.allTabs.length;
+ await be_in_folder(folder);
+
+ // Get a reference to a bunch of headers
+ let msgHdrs = msgHdrsInFolder.slice(0, NUM_MESSAGES_TO_OPEN);
+
+ // Open them
+ MailUtils.displayMessages(msgHdrs);
+ // This is going to trigger a message display in the main 3pane window. Since
+ // the message will open in a new tab, we shouldn't
+ // plan_for_message_display().
+ wait_for_message_display_completion(mc, true);
+ // Check that the tab count has increased by the correct number
+ assert_number_of_tabs_open(preCount + NUM_MESSAGES_TO_OPEN);
+ // Check that the currently displayed tab is a message tab (i.e. one of our
+ // newly opened tabs is in the foreground)
+ assert_tab_mode_name(null, "mailMessageTab");
+
+ // Now check whether each of the NUM_MESSAGES_TO_OPEN tabs has the correct
+ // title
+ for (let i = 0; i < NUM_MESSAGES_TO_OPEN; i++) {
+ assert_tab_titled_from(
+ mc.window.document.getElementById("tabmail").tabInfo[preCount + i],
+ msgHdrs[i]
+ );
+ }
+
+ // Check whether each tab has the correct message and whether the message pane
+ // is focused in each case, then close it to load the previous tab.
+ for (let i = 0; i < NUM_MESSAGES_TO_OPEN; i++) {
+ assert_selected_and_displayed(msgHdrs.pop());
+ assert_message_pane_focused();
+ close_tab(mc.window.document.getElementById("tabmail").currentTabInfo);
+ }
+ await switch_tab(folderTab);
+ reset_open_message_behavior();
+}
+add_task(test_open_multiple_messages_without_backing_views_in_tabs);
+
+/**
+ * Test opening a message without a backing view in a new window.
+ */
+async function test_open_message_without_backing_view_in_new_window() {
+ set_open_message_behavior("NEW_WINDOW");
+ await be_in_folder(folder);
+
+ // Select a message
+ let msgHdr = msgHdrsInFolder[6];
+
+ let newWindowPromise = async_plan_for_new_window("mail:messageWindow");
+ // Open it
+ MailUtils.displayMessage(msgHdr);
+ let msgc = await newWindowPromise;
+ wait_for_message_display_completion(msgc, true);
+
+ assert_selected_and_displayed(msgc, msgHdr);
+ // Clean up, close the window
+ close_message_window(msgc);
+ reset_open_message_behavior();
+}
+add_task(test_open_message_without_backing_view_in_new_window).skip(); // TODO
+
+/**
+ * Test reusing an existing window to open a new message.
+ */
+async function test_open_message_without_backing_view_in_existing_window() {
+ set_open_message_behavior("EXISTING_WINDOW");
+ await be_in_folder(folder);
+
+ // Open up a window
+ let firstMsgHdr = msgHdrsInFolder[3];
+ let newWindowPromise = async_plan_for_new_window("mail:messageWindow");
+ MailUtils.displayMessage(firstMsgHdr);
+ let msgc = await newWindowPromise;
+ wait_for_message_display_completion(msgc, true);
+
+ // Open another message
+ let msgHdr = msgHdrsInFolder[7];
+ plan_for_message_display(msgc);
+ MailUtils.displayMessage(msgHdr);
+ wait_for_message_display_completion(msgc, true);
+
+ // Check if our old window displays the message
+ assert_selected_and_displayed(msgc, msgHdr);
+ // Clean up, close the window
+ close_message_window(msgc);
+ reset_open_message_behavior();
+}
+add_task(test_open_message_without_backing_view_in_existing_window).skip(); // TODO
+
+/**
+ * Time to throw a spanner in the works. Set a mail view for the folder that
+ * excludes every message.
+ */
+add_task(function test_filter_out_all_messages() {
+ set_mail_view(MailViewConstants.kViewItemTags, "$label1");
+ // Make sure all the messages have actually disappeared
+ assert_messages_not_in_view(msgHdrsInFolder);
+});
+
+/**
+ * Re-run all the tests.
+ */
+add_task(
+ async function test_open_single_message_without_backing_view_in_tab_filtered() {
+ await test_open_single_message_without_backing_view_in_tab();
+ }
+);
+
+add_task(
+ async function test_open_multiple_messages_without_backing_views_in_tabs_filtered() {
+ await test_open_multiple_messages_without_backing_views_in_tabs();
+ }
+);
+
+add_task(
+ async function test_open_message_without_backing_view_in_new_window_filtered() {
+ await test_open_message_without_backing_view_in_new_window();
+ }
+).skip(); // TODO
+
+add_task(
+ async function test_open_message_without_backing_view_in_existing_window_filtered() {
+ await test_open_message_without_backing_view_in_existing_window();
+ }
+).skip(); // TODO
+
+/**
+ * Good hygiene: remove the view picker from the toolbar.
+ */
+add_task(function test_cleanup() {
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_readMsgs.js b/comm/mail/test/browser/folder-display/browser_readMsgs.js
new file mode 100644
index 0000000000..1f69bfbe54
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_readMsgs.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests various special messages.
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var {
+ assert_selected_and_displayed,
+ be_in_folder,
+ create_folder,
+ inboxFolder,
+ press_delete,
+ select_click_row,
+ wait_for_message_display_completion,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+/**
+ * Tests that a message containing an invalid vcard can be displayed.
+ */
+add_task(async function testMarkedAsRead() {
+ let folder = await create_folder("SpecialMsgs");
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", true);
+
+ let file = new FileUtils.File(getTestFilePath("data/test-invalid-vcard.eml"));
+ Assert.ok(file.exists(), "test data file should exist");
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ // Copy gIncomingMailFile into the Inbox.
+ MailServices.copy.copyFileMessage(
+ file,
+ folder,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+ await be_in_folder(folder);
+ let msg = select_click_row(0);
+ assert_selected_and_displayed(0);
+ // Make sure it's the msg we want.
+ Assert.equal(msg.subject, "this contains an invalid vcard");
+ // The message should get marked as read.
+ await BrowserTestUtils.waitForCondition(
+ () => msg.isRead,
+ "should get marked as read"
+ );
+ await be_in_folder(inboxFolder);
+ folder.deleteSelf(null);
+ Services.prefs.clearUserPref("mailnews.mark_message_read.auto");
+});
diff --git a/comm/mail/test/browser/folder-display/browser_recentMenu.js b/comm/mail/test/browser/folder-display/browser_recentMenu.js
new file mode 100644
index 0000000000..d87b119b0f
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_recentMenu.js
@@ -0,0 +1,195 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This tests the move/copy to recent folder menus to make sure
+ * that they get updated when messages are moved to folders, and
+ * don't get updated when we archive.
+ */
+
+"use strict";
+
+var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");
+var {
+ archive_selected_messages,
+ be_in_folder,
+ create_folder,
+ get_special_folder,
+ make_message_sets_in_folders,
+ mc,
+ press_delete,
+ right_click_on_row,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var {
+ click_menus_in_sequence,
+ close_popup_sequence,
+ click_appmenu_in_sequence,
+} = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var folder1, folder2;
+var gInitRecentMenuCount;
+
+add_setup(async function () {
+ // Ensure that there are no updated folders to ensure the recent folder
+ // is empty.
+ for (let folder of MailServices.accounts.allFolders) {
+ folder.setStringProperty("MRMTime", "0");
+ }
+
+ // Try to make these folders first in alphabetic order
+ folder1 = await create_folder("aaafolder1");
+ folder2 = await create_folder("aaafolder2");
+
+ await make_message_sets_in_folders([folder1], [{ count: 3 }]);
+});
+
+add_task(async function test_move_message() {
+ await be_in_folder(folder1);
+ let msgHdr = select_click_row(0);
+ // This will cause the initial build of the move recent context menu,
+ // which should be empty and disabled.
+ await right_click_on_row(0);
+ let popups = await click_menus_in_sequence(
+ getMailContext(),
+ [{ id: "mailContext-moveMenu" }, { label: "Recent" }],
+ true
+ );
+ let recentMenu = popups[popups.length - 2].querySelector('[label="Recent"]');
+ Assert.equal(recentMenu.getAttribute("disabled"), "true");
+ gInitRecentMenuCount = recentMenu.itemCount;
+ Assert.equal(gInitRecentMenuCount, 0);
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ close_popup_sequence(popups);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ let copyListener = {
+ copyDone: false,
+ OnStartCopy() {},
+ OnProgress(aProgress, aProgressMax) {},
+ SetMessageKey(aKey) {},
+ SetMessageId(aMessageId) {},
+ OnStopCopy(aStatus) {
+ this.copyDone = true;
+ },
+ };
+ MailServices.copy.copyMessages(
+ folder1,
+ [msgHdr],
+ folder2,
+ true,
+ copyListener,
+ mc.window.msgWindow,
+ true
+ );
+ utils.waitFor(
+ () => copyListener.copyDone,
+ "Timeout waiting for copy to complete",
+ 10000,
+ 100
+ );
+ // We've moved a message to aaafolder2 - it should appear in recent list now.
+ // Clicking the menuitem by label is not localizable, but Recent doesn't have an
+ // id we can use.
+ select_click_row(0);
+ await right_click_on_row(0);
+ popups = await click_menus_in_sequence(
+ getMailContext(),
+ [{ id: "mailContext-moveMenu" }, { label: "Recent" }],
+ true
+ );
+ let recentChildren = popups[popups.length - 1].children;
+ Assert.equal(
+ recentChildren.length,
+ gInitRecentMenuCount + 1,
+ "recent menu should have one more child after move"
+ );
+ Assert.equal(
+ recentChildren[0].label,
+ "aaafolder2",
+ "recent menu child should be aaafolder2 after move"
+ );
+ hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ close_popup_sequence(popups);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+});
+
+add_task(async function test_delete_message() {
+ press_delete(mc);
+ // We've deleted a message - we should still just have folder2 in the menu.
+ select_click_row(0); // TODO shouldn't need to do this
+ await right_click_on_row(0);
+ let popups = await click_menus_in_sequence(
+ getMailContext(),
+ [{ id: "mailContext-moveMenu" }, { label: "Recent" }],
+ true
+ );
+ let recentChildren = popups[popups.length - 1].children;
+ Assert.equal(
+ recentChildren.length,
+ gInitRecentMenuCount + 1,
+ "delete shouldn't add anything to recent menu"
+ );
+ Assert.equal(
+ recentChildren[0].label,
+ "aaafolder2",
+ "recent menu should still be aaafolder2 after delete"
+ );
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ close_popup_sequence(popups);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+});
+
+add_task(async function test_archive_message() {
+ archive_selected_messages();
+ // We've archived a message - we should still just have folder2 in the menu.
+ let archive = await get_special_folder(
+ Ci.nsMsgFolderFlags.Archive,
+ false,
+ false
+ );
+ await be_in_folder(archive.descendants[0]);
+ select_click_row(0);
+ await right_click_on_row(0);
+ let popups = await click_menus_in_sequence(
+ getMailContext(),
+ [{ id: "mailContext-moveMenu" }, { label: "Recent" }],
+ true
+ );
+ let recentChildren = popups[popups.length - 1].children;
+ Assert.equal(
+ recentChildren.length,
+ gInitRecentMenuCount + 1,
+ "archive shouldn't add anything to recent menu"
+ );
+ Assert.equal(
+ recentChildren[0].label,
+ "aaafolder2",
+ "recent menu should still be aaafolder2 after archive"
+ );
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ getMailContext(),
+ "popuphidden"
+ );
+ close_popup_sequence(popups);
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+});
diff --git a/comm/mail/test/browser/folder-display/browser_rightClickMiddleClickFolders.js b/comm/mail/test/browser/folder-display/browser_rightClickMiddleClickFolders.js
new file mode 100644
index 0000000000..5c69265127
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_rightClickMiddleClickFolders.js
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test the many horrors involving right-clicks, middle clicks, and
+ * selections... on folders!
+ */
+
+"use strict";
+
+var {
+ assert_folder_displayed,
+ assert_folder_selected,
+ assert_folder_selected_and_displayed,
+ assert_folders_selected_and_displayed,
+ assert_selected_tab,
+ be_in_folder,
+ close_popup,
+ close_tab,
+ create_folder,
+ make_message_sets_in_folders,
+ mc,
+ middle_click_on_folder,
+ reset_context_menu_background_tabs,
+ right_click_on_folder,
+ select_click_folder,
+ select_shift_click_folder,
+ set_context_menu_background_tabs,
+ switch_tab,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folderA, folderB, folderC;
+
+add_setup(async function () {
+ folderA = await create_folder("RightClickMiddleClickFoldersA");
+ folderB = await create_folder("RightClickMiddleClickFoldersB");
+ folderC = await 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
+
+ await make_message_sets_in_folders([folderA], [{ count: 1 }]);
+ await make_message_sets_in_folders([folderB], [{ count: 1 }]);
+ await make_message_sets_in_folders([folderC], [{ count: 1 }]);
+});
+
+/**
+ * One-thing selected, right-click on something else.
+ */
+add_task(async function test_right_click_folder_with_one_thing_selected() {
+ select_click_folder(folderB);
+ assert_folder_selected_and_displayed(folderB);
+
+ await right_click_on_folder(folderA);
+ assert_folder_selected(folderA);
+ assert_folder_displayed(folderB);
+
+ await close_popup(mc, getFoldersContext());
+ assert_folder_selected_and_displayed(folderB);
+}).skip();
+
+/**
+ * Many things selected, right-click on something that is not in that selection.
+ */
+add_task(async 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);
+
+ await right_click_on_folder(folderC);
+ assert_folder_selected(folderC);
+ assert_folder_displayed(folderA);
+
+ await close_popup(mc, getFoldersContext());
+ assert_folders_selected_and_displayed(folderA, folderB);
+}).skip();
+
+/**
+ * One thing selected, right-click on that.
+ */
+add_task(async function test_right_click_folder_on_existing_single_selection() {
+ select_click_folder(folderA);
+ assert_folders_selected_and_displayed(folderA);
+
+ await right_click_on_folder(folderA);
+ assert_folders_selected_and_displayed(folderA);
+
+ await close_popup(mc, getFoldersContext());
+ assert_folders_selected_and_displayed(folderA);
+});
+
+/**
+ * Many things selected, right-click somewhere in the selection.
+ */
+add_task(async 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);
+
+ await right_click_on_folder(folderC);
+ assert_folders_selected_and_displayed(folderB, folderC);
+
+ await close_popup(mc, getFoldersContext());
+ assert_folders_selected_and_displayed(folderB, folderC);
+}).skip();
+
+/**
+ * One-thing selected, middle-click on something else.
+ */
+async function _middle_click_folder_with_one_thing_selected_helper(
+ aBackground
+) {
+ select_click_folder(folderB);
+ assert_folder_selected_and_displayed(folderB);
+
+ let originalTab = mc.window.document.getElementById("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
+ await switch_tab(newTab);
+ }
+ assert_folder_selected_and_displayed(folderA);
+ close_tab(newTab);
+
+ assert_folder_selected_and_displayed(folderB);
+}
+
+async 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.window.document.getElementById("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
+ await 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.
+ */
+async function _middle_click_folder_on_existing_single_selection_helper(
+ aBackground
+) {
+ select_click_folder(folderC);
+ assert_folder_selected_and_displayed(folderC);
+
+ let originalTab = mc.window.document.getElementById("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
+ await 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.
+ */
+async 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.window.document.getElementById("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
+ await 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);
+}
+
+/**
+ * Middle click on target folder when a folder is selected and displayed.
+ */
+async function middle_click_helper(selectedFolder, targetFolder, shiftPressed) {
+ select_click_folder(selectedFolder);
+ assert_folders_selected_and_displayed(selectedFolder);
+ let originalTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+
+ let [newTab] = middle_click_on_folder(targetFolder, shiftPressed);
+
+ if (shiftPressed) {
+ assert_selected_tab(newTab);
+ } else {
+ // Make sure we haven't switched to the new tab.
+ assert_selected_tab(originalTab);
+ // Now switch to the new tab and check the tab was switched.
+ await switch_tab(newTab);
+ }
+ close_tab(newTab);
+ assert_folders_selected_and_displayed(selectedFolder);
+}
+
+add_task(async function middle_click_tests() {
+ select_click_folder(folderA);
+ assert_folders_selected_and_displayed(folderA);
+
+ // middle clicks while pressing shift
+ await middle_click_helper(folderA, folderA, true);
+ await middle_click_helper(folderA, folderB, true);
+
+ // middle clicks without pressing shift
+ await middle_click_helper(folderA, folderA, false);
+ await middle_click_helper(folderA, folderB, false);
+});
+
+/**
+ * Generate background and foreground tests for each middle click test.
+ *
+ * @param aTests an array of test names
+ */
+var global = this;
+function _generate_background_foreground_tests(aTests) {
+ for (let test of aTests) {
+ let helperFunc = global["_" + test + "_helper"];
+ global["test_" + test + "_background"] = async function () {
+ set_context_menu_background_tabs(true);
+ await helperFunc(true);
+ reset_context_menu_background_tabs();
+ };
+ global["test_" + test + "_foreground"] = async function () {
+ set_context_menu_background_tabs(false);
+ await helperFunc(false);
+ reset_context_menu_background_tabs();
+ };
+ add_task(global[`test_${test}_background`]).skip();
+ add_task(global[`test_${test}_foreground`]).skip();
+ }
+}
+
+_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",
+]);
+
+add_task(() => {
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_rightClickMiddleClickMessages.js b/comm/mail/test/browser/folder-display/browser_rightClickMiddleClickMessages.js
new file mode 100644
index 0000000000..50033f0e7e
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_rightClickMiddleClickMessages.js
@@ -0,0 +1,564 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test the many horrors involving right-clicks, middle clicks, and selections.
+ */
+
+"use strict";
+
+var {
+ add_message_sets_to_folders,
+ assert_displayed,
+ assert_message_not_in_view,
+ assert_message_pane_focused,
+ assert_messages_not_in_view,
+ assert_nothing_selected,
+ assert_selected,
+ assert_selected_and_displayed,
+ assert_selected_tab,
+ assert_thread_tree_focused,
+ be_in_folder,
+ close_popup,
+ close_tab,
+ collapse_all_threads,
+ create_folder,
+ create_thread,
+ delete_via_popup,
+ expand_all_threads,
+ focus_thread_tree,
+ get_about_3pane,
+ make_display_threaded,
+ make_message_sets_in_folders,
+ mc,
+ middle_click_on_row,
+ reset_context_menu_background_tabs,
+ right_click_on_row,
+ select_click_row,
+ select_none,
+ select_shift_click_row,
+ set_context_menu_background_tabs,
+ switch_tab,
+ wait_for_message_display_completion,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folder, threadedFolder;
+var tabmail = document.getElementById("tabmail");
+
+/**
+ * The number of messages in the thread we use to test.
+ */
+var NUM_MESSAGES_IN_THREAD = 6;
+
+add_setup(async function () {
+ folder = await create_folder("RightClickMiddleClickA");
+ threadedFolder = await 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.
+ await make_message_sets_in_folders([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.)
+ await make_message_sets_in_folders([threadedFolder], [{ count: 50 }]);
+ let thread = create_thread(NUM_MESSAGES_IN_THREAD);
+ await add_message_sets_to_folders([threadedFolder], [thread]);
+
+ registerCleanupFunction(function () {
+ reset_context_menu_background_tabs();
+ });
+});
+
+/**
+ * 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.
+ */
+add_task(async function test_right_click_with_nothing_selected() {
+ await be_in_folder(folder);
+
+ select_none();
+ assert_nothing_selected();
+
+ await right_click_on_row(1);
+ // Check that the popup opens.
+ await wait_for_popup_to_open(getMailContext());
+
+ assert_selected(1);
+ assert_displayed();
+
+ await close_popup(mc, getMailContext());
+ assert_nothing_selected();
+}).skip();
+
+/**
+ * Test that clicking on the column header shows the column picker.
+ */
+add_task(async function test_right_click_column_header_shows_col_picker() {
+ await be_in_folder(folder);
+
+ // The treecolpicker element itself doesn't have an id, so we have to walk
+ // down from the parent to find it.
+ // treadCols
+ // |- hbox item 0
+ // |- treecolpicker <-- item 1 this is the one we want
+ let threadCols = mc.window.document.getElementById("threadCols");
+ let treeColPicker = threadCols.querySelector("treecolpicker");
+ let popup = treeColPicker.querySelector("[anonid=popup]");
+
+ // Right click the subject column header
+ // This should show the column picker popup.
+ let subjectCol = mc.window.document.getElementById("subjectCol");
+ EventUtils.synthesizeMouseAtCenter(
+ subjectCol,
+ { type: "contextmenu", button: 2 },
+ subjectCol.ownerGlobal
+ );
+
+ // Check that the popup opens.
+ await wait_for_popup_to_open(popup);
+ // Hide it again, we just wanted to know it was going to be shown.
+ await close_popup(mc, popup);
+}).skip();
+
+/**
+ * One-thing selected, right-click on something else.
+ */
+add_task(async function test_right_click_with_one_thing_selected() {
+ await be_in_folder(folder);
+
+ select_click_row(0);
+ assert_selected_and_displayed(0);
+
+ await right_click_on_row(1);
+ assert_selected(1);
+ assert_displayed(0);
+
+ await close_popup(mc, getMailContext());
+ assert_selected_and_displayed(0);
+}).skip();
+
+/**
+ * Many things selected, right-click on something that is not in that selection.
+ */
+add_task(async function test_right_click_with_many_things_selected() {
+ await be_in_folder(folder);
+
+ select_click_row(0);
+ select_shift_click_row(5);
+ assert_selected_and_displayed([0, 5]);
+
+ await right_click_on_row(6);
+ assert_selected(6);
+ assert_displayed([0, 5]);
+
+ await close_popup(mc, getMailContext());
+ assert_selected_and_displayed([0, 5]);
+}).skip();
+
+/**
+ * One thing selected, right-click on that.
+ */
+add_task(async function test_right_click_on_existing_single_selection() {
+ await be_in_folder(folder);
+
+ select_click_row(3);
+ assert_selected_and_displayed(3);
+
+ await right_click_on_row(3);
+ assert_selected_and_displayed(3);
+
+ await close_popup(mc, getMailContext());
+ assert_selected_and_displayed(3);
+});
+
+/**
+ * Many things selected, right-click somewhere in the selection.
+ */
+add_task(async function test_right_click_on_existing_multi_selection() {
+ await be_in_folder(folder);
+
+ select_click_row(3);
+ select_shift_click_row(6);
+ assert_selected_and_displayed([3, 6]);
+
+ await right_click_on_row(5);
+ assert_selected_and_displayed([3, 6]);
+
+ await close_popup(mc, getMailContext());
+ assert_selected_and_displayed([3, 6]);
+});
+
+/**
+ * Middle clicking should open a message in a tab, but not affect our selection.
+ */
+async function _middle_click_with_nothing_selected_helper(aBackground) {
+ await be_in_folder(folder);
+
+ select_none();
+ assert_nothing_selected();
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ // Focus the thread tree -- we're going to make sure it's focused when we
+ // come back
+ focus_thread_tree();
+ 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
+ await switch_tab(tabMessage);
+ } else {
+ wait_for_message_display_completion();
+ }
+
+ assert_selected_and_displayed(curMessage);
+ assert_message_pane_focused();
+ close_tab(tabMessage);
+
+ assert_nothing_selected();
+ assert_thread_tree_focused();
+}
+
+add_task(async function test_middle_click_with_nothing_selected_fg() {
+ set_context_menu_background_tabs(false);
+ await _middle_click_with_nothing_selected_helper(false);
+});
+
+add_task(async function test_middle_click_with_nothing_selected_bg() {
+ set_context_menu_background_tabs(true);
+ await _middle_click_with_nothing_selected_helper(true);
+});
+
+/**
+ * One-thing selected, middle-click on something else.
+ */
+async function _middle_click_with_one_thing_selected_helper(aBackground) {
+ await be_in_folder(folder);
+
+ select_click_row(0);
+ assert_selected_and_displayed(0);
+
+ let folderTab = mc.window.document.getElementById("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
+ await switch_tab(tabMessage);
+ } else {
+ wait_for_message_display_completion();
+ }
+
+ assert_selected_and_displayed(curMessage);
+ assert_message_pane_focused();
+ close_tab(tabMessage);
+
+ assert_selected_and_displayed(0);
+ assert_thread_tree_focused();
+}
+
+add_task(async function test_middle_click_with_one_thing_selected_fg() {
+ set_context_menu_background_tabs(false);
+ await _middle_click_with_one_thing_selected_helper(false);
+});
+
+add_task(async function test_middle_click_with_one_thing_selected_bg() {
+ set_context_menu_background_tabs(true);
+ await _middle_click_with_one_thing_selected_helper(true);
+});
+
+/**
+ * Many things selected, middle-click on something that is not in that
+ * selection.
+ */
+async function _middle_click_with_many_things_selected_helper(aBackground) {
+ await be_in_folder(folder);
+
+ select_click_row(0);
+ select_shift_click_row(5);
+ assert_selected_and_displayed([0, 5]);
+
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ let [tabMessage] = 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
+ await switch_tab(tabMessage);
+ } else {
+ wait_for_message_display_completion();
+ }
+
+ assert_message_pane_focused();
+ tabmail.closeOtherTabs(0);
+
+ assert_selected_and_displayed([0, 5]);
+ assert_thread_tree_focused();
+}
+
+add_task(async function test_middle_click_with_many_things_selected_fg() {
+ set_context_menu_background_tabs(false);
+ await _middle_click_with_many_things_selected_helper(false);
+});
+
+add_task(async function test_middle_click_with_many_things_selected_bg() {
+ set_context_menu_background_tabs(true);
+ await _middle_click_with_many_things_selected_helper(true);
+});
+
+/**
+ * One thing selected, middle-click on that.
+ */
+async function _middle_click_on_existing_single_selection_helper(aBackground) {
+ await be_in_folder(folder);
+
+ select_click_row(3);
+ assert_selected_and_displayed(3);
+
+ let folderTab = mc.window.document.getElementById("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
+ await switch_tab(tabMessage);
+ } else {
+ wait_for_message_display_completion();
+ }
+
+ assert_selected_and_displayed(curMessage);
+ assert_message_pane_focused();
+ close_tab(tabMessage);
+
+ assert_selected_and_displayed(3);
+ assert_thread_tree_focused();
+}
+
+add_task(async function test_middle_click_on_existing_single_selection_fg() {
+ set_context_menu_background_tabs(false);
+ await _middle_click_on_existing_single_selection_helper(false);
+});
+
+add_task(async function test_middle_click_on_existing_single_selection_bg() {
+ set_context_menu_background_tabs(true);
+ await _middle_click_on_existing_single_selection_helper(true);
+});
+
+/**
+ * Many things selected, middle-click somewhere in the selection.
+ */
+async function _middle_click_on_existing_multi_selection_helper(aBackground) {
+ await be_in_folder(folder);
+
+ select_click_row(3);
+ select_shift_click_row(6);
+ assert_selected_and_displayed([3, 6]);
+
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+ let [tabMessage, curMessage] = middle_click_on_row(6);
+ if (aBackground) {
+ // Make sure we haven't switched to the new tab.
+ assert_selected_tab(folderTab);
+ // Now switch to the new tab and check
+ await switch_tab(tabMessage);
+ } else {
+ wait_for_message_display_completion();
+ }
+
+ assert_selected_and_displayed(curMessage);
+ assert_message_pane_focused();
+ tabmail.closeOtherTabs(0);
+
+ assert_selected_and_displayed([3, 6]);
+ assert_thread_tree_focused();
+}
+
+add_task(async function test_middle_click_on_existing_multi_selection_fg() {
+ set_context_menu_background_tabs(false);
+ await _middle_click_on_existing_multi_selection_helper(false);
+});
+
+add_task(async function test_middle_click_on_existing_multi_selection_bg() {
+ set_context_menu_background_tabs(true);
+ await _middle_click_on_existing_multi_selection_helper(true);
+});
+
+/**
+ * Middle-click on the root of a collapsed thread, making sure that we don't
+ * jump around in the thread tree.
+ */
+async function _middle_click_on_collapsed_thread_root_helper(aBackground) {
+ await be_in_folder(threadedFolder);
+ make_display_threaded();
+ collapse_all_threads();
+
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+
+ let tree = get_about_3pane().threadTree;
+ // Note the first visible row
+ let preFirstRow = tree.getFirstVisibleIndex();
+
+ // Since reflowing a tree (eg when switching tabs) ensures that the current
+ // index is brought into view, we need to set the current index so that we
+ // don't scroll because of it. So click on the first visible row.
+ select_click_row(preFirstRow);
+
+ if (!aBackground) {
+ wait_for_message_display_completion();
+ // Switch back to the folder tab
+ await switch_tab(folderTab);
+ }
+ if (tree.getFirstVisibleIndex() != preFirstRow) {
+ throw new Error(
+ "The first visible row should have been " +
+ preFirstRow +
+ ", but is actually " +
+ tree.getFirstVisibleIndex() +
+ "."
+ );
+ }
+ tabmail.closeOtherTabs(0);
+}
+
+add_task(async function test_middle_click_on_collapsed_thread_root_fg() {
+ set_context_menu_background_tabs(false);
+ await _middle_click_on_collapsed_thread_root_helper(false);
+});
+
+add_task(async function test_middle_click_on_collapsed_thread_root_bg() {
+ set_context_menu_background_tabs(true);
+ await _middle_click_on_collapsed_thread_root_helper(true);
+});
+
+/**
+ * Middle-click on the root of an expanded thread, making sure that we don't
+ * jump around in the thread tree.
+ */
+async function _middle_click_on_expanded_thread_root_helper(aBackground) {
+ await be_in_folder(threadedFolder);
+ make_display_threaded();
+ expand_all_threads();
+
+ let folderTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+
+ let tree = get_about_3pane().threadTree;
+ // Note the first visible row
+ let preFirstRow = tree.getFirstVisibleIndex();
+
+ // Since reflowing a tree (eg when switching tabs) ensures that the current
+ // index is brought into view, we need to set the current index so that we
+ // don't scroll because of it. So click on the first visible row.
+ select_click_row(preFirstRow);
+
+ // 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(
+ tree.view.rowCount - NUM_MESSAGES_IN_THREAD
+ );
+
+ if (!aBackground) {
+ wait_for_message_display_completion();
+ // Switch back to the folder tab
+ await switch_tab(folderTab);
+ }
+
+ // Make sure the first visible row is still the same
+ if (tree.getFirstVisibleIndex() != preFirstRow) {
+ throw new Error(
+ "The first visible row should have been " +
+ preFirstRow +
+ ", but is actually " +
+ tree.getFirstVisibleIndex() +
+ "."
+ );
+ }
+
+ close_tab(tabMessage);
+}
+
+add_task(async function test_middle_click_on_expanded_thread_root_fg() {
+ set_context_menu_background_tabs(false);
+ await _middle_click_on_expanded_thread_root_helper(false);
+});
+
+add_task(async function test_middle_click_on_expanded_thread_root_bg() {
+ set_context_menu_background_tabs(true);
+ await _middle_click_on_expanded_thread_root_helper(true);
+});
+
+/**
+ * Right-click on something and delete it, having no selection previously.
+ */
+add_task(async function test_right_click_deletion_nothing_selected() {
+ await be_in_folder(folder);
+
+ select_none();
+ assert_selected_and_displayed();
+
+ let delMessage = await right_click_on_row(3);
+ await 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();
+}).skip();
+
+/**
+ * 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.
+ */
+add_task(async function test_right_click_deletion_one_other_thing_selected() {
+ await be_in_folder(folder);
+
+ let curMessage = select_click_row(5);
+
+ let delMessage = await right_click_on_row(3);
+ await delete_via_popup();
+ assert_message_not_in_view(delMessage);
+
+ assert_selected_and_displayed(curMessage);
+}).skip();
+
+add_task(async function test_right_click_deletion_many_other_things_selected() {
+ await be_in_folder(folder);
+
+ select_click_row(4);
+ let messages = select_shift_click_row(6);
+
+ let delMessage = await right_click_on_row(2);
+ await delete_via_popup();
+ assert_message_not_in_view(delMessage);
+
+ assert_selected_and_displayed(messages);
+}).skip();
+
+add_task(async function test_right_click_deletion_of_one_selected_thing() {
+ await be_in_folder(folder);
+
+ let curMessage = select_click_row(2);
+
+ await right_click_on_row(2);
+ await delete_via_popup();
+ assert_message_not_in_view(curMessage);
+
+ // Assert.notEqual(mc.window.document.getElementById("tabmail").currentTabInfo.browser.contentWindow.gDBView.selection.count, 0, "We should have tried to select something!");
+});
+
+add_task(async function test_right_click_deletion_of_many_selected_things() {
+ await be_in_folder(folder);
+
+ select_click_row(2);
+ let messages = select_shift_click_row(4);
+
+ await right_click_on_row(3);
+ await delete_via_popup();
+ assert_messages_not_in_view(messages);
+
+ // Assert.notEqual(mc.window.document.getElementById("tabmail").currentTabInfo.browser.contentWindow.gDBView.selection.count, 0, "We should have tried to select something!");
+});
diff --git a/comm/mail/test/browser/folder-display/browser_savedsearchReloadAfterCompact.js b/comm/mail/test/browser/folder-display/browser_savedsearchReloadAfterCompact.js
new file mode 100644
index 0000000000..262e4b3d94
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_savedsearchReloadAfterCompact.js
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test reload of saved searches over local folders after compaction
+ * of local folders.
+ */
+
+"use strict";
+
+var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");
+var { gThreadManager } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Maild.jsm"
+);
+
+var {
+ be_in_folder,
+ create_folder,
+ create_virtual_folder,
+ get_about_3pane,
+ inboxFolder,
+ make_message_sets_in_folders,
+ mc,
+ press_delete,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var otherFolder;
+var folderVirtual;
+var synSets;
+
+/**
+ * Add some messages to a folder, delete the first one, and create a saved
+ * search over the inbox and the folder. Then, compact folders.
+ */
+add_task(async function test_setup_virtual_folder_and_compact() {
+ otherFolder = await create_folder();
+ synSets = await make_message_sets_in_folders([otherFolder], [{ count: 2 }]);
+
+ /**
+ * We delete the first message in the local folder, so compaction of the
+ * folder will invalidate the key of the second message in the folder. Then,
+ * we select the second message and issue the compact. This causes saving the
+ * selection on the compaction notification to fail. We test the saved search
+ * view still gets rebuilt, such that there is a valid msg hdr at row 0.
+ */
+ await be_in_folder(otherFolder);
+ select_click_row(0);
+ press_delete();
+
+ folderVirtual = create_virtual_folder(
+ [inboxFolder, otherFolder],
+ {},
+ true,
+ "SavedSearch"
+ );
+
+ await be_in_folder(folderVirtual);
+ select_click_row(0);
+ let urlListener = {
+ compactDone: false,
+
+ OnStartRunningUrl(aUrl) {},
+ OnStopRunningUrl(aUrl, aExitCode) {
+ this.compactDone = true;
+ },
+ };
+ if (otherFolder.msgStore.supportsCompaction) {
+ otherFolder.compactAll(urlListener, null);
+
+ utils.waitFor(
+ () => urlListener.compactDone,
+ "Timeout waiting for compact to complete",
+ 10000,
+ 100
+ );
+ }
+ // Let the event queue clear.
+ await new Promise(resolve => setTimeout(resolve));
+ // Check view is still valid
+ get_about_3pane().gDBView.getMsgHdrAt(0);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
+
+add_task(async function endTest() {
+ // Fixing possible nsIMsgDBHdr.markHasAttachments onEndMsgDownload runs.
+ // Found in chaosmode.
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ // Cleanup dbView with force.
+ get_about_3pane().gDBView.close(true);
+ folderVirtual.deleteSelf(null);
+ otherFolder.deleteSelf(null);
+});
diff --git a/comm/mail/test/browser/folder-display/browser_selection.js b/comm/mail/test/browser/folder-display/browser_selection.js
new file mode 100644
index 0000000000..09885f1f92
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_selection.js
@@ -0,0 +1,202 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var {
+ assert_nothing_selected,
+ assert_selected_and_displayed,
+ assert_visible,
+ be_in_folder,
+ close_tab,
+ create_folder,
+ delete_via_popup,
+ enter_folder,
+ get_about_3pane,
+ make_display_grouped,
+ make_display_threaded,
+ make_display_unthreaded,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_tab,
+ press_delete,
+ right_click_on_row,
+ select_click_row,
+ select_column_click_row,
+ select_control_click_row,
+ select_none,
+ select_shift_click_row,
+ switch_tab,
+ wait_for_blank_content_pane,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+// let us have 2 folders
+var folder = null,
+ folder2 = null;
+
+add_setup(async function () {
+ folder = await create_folder("SelectionA");
+ folder2 = await create_folder("SelectionB");
+ await make_message_sets_in_folders([folder, folder2], [{ count: 50 }]);
+});
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c80
+add_task(async function test_selection_on_entry() {
+ await enter_folder(folder);
+ assert_nothing_selected();
+});
+
+add_task(async function test_selection_extension() {
+ await be_in_folder(folder);
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c79 (was good)
+ select_click_row(1);
+ select_control_click_row(2);
+ press_delete();
+ assert_selected_and_displayed(1);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c79 (was bad)
+ select_click_row(2);
+ select_control_click_row(1);
+ press_delete();
+ assert_selected_and_displayed(1);
+
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c87 first bit
+ press_delete();
+ assert_selected_and_displayed(1);
+});
+
+add_task(async function test_selection_select_column() {
+ await be_in_folder(folder);
+ mc.window.document.getElementById("selectCol").removeAttribute("hidden");
+ select_none();
+ select_column_click_row(0);
+ assert_selected_and_displayed(0);
+ select_column_click_row(0);
+ assert_nothing_selected();
+ select_column_click_row(2);
+ select_column_click_row(3);
+ select_column_click_row(4);
+ // This only takes a range.
+ assert_selected_and_displayed([2, 4]); // ensures multi-message summary
+ select_column_click_row(2);
+ assert_selected_and_displayed([3, 4]); // ensures multi-message summary
+ select_column_click_row(3);
+ assert_selected_and_displayed(4);
+ select_column_click_row(4);
+ assert_nothing_selected();
+}).skip();
+
+add_task(async function test_selection_select_column_deselection() {
+ await be_in_folder(folder);
+ select_none();
+ select_column_click_row(3);
+ select_column_click_row(3);
+ assert_nothing_selected();
+ await right_click_on_row(7);
+ await delete_via_popup();
+ assert_nothing_selected();
+ mc.window.document.getElementById("selectCol").setAttribute("hidden", true);
+}).skip();
+
+add_task(async function test_selection_last_message_deleted() {
+ await be_in_folder(folder);
+ select_click_row(-1);
+ press_delete();
+ assert_selected_and_displayed(-1);
+});
+
+add_task(async function test_selection_persists_through_threading_changes() {
+ await be_in_folder(folder);
+
+ make_display_unthreaded();
+ let message = select_click_row(3);
+ make_display_threaded();
+ assert_selected_and_displayed(message);
+ make_display_grouped();
+ assert_selected_and_displayed(message);
+});
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=474701#c82 2nd half
+add_task(async function test_no_selection_persists_through_threading_changes() {
+ await be_in_folder(folder);
+
+ make_display_unthreaded();
+ select_none();
+ make_display_threaded();
+ assert_nothing_selected();
+ make_display_grouped();
+ assert_nothing_selected();
+ make_display_unthreaded();
+});
+
+add_task(async function test_selection_persists_through_folder_tab_changes() {
+ let tab1 = await be_in_folder(folder);
+
+ select_click_row(2);
+
+ let tab2 = await open_folder_in_new_tab(folder2);
+ wait_for_blank_content_pane();
+ assert_nothing_selected();
+
+ await switch_tab(tab1);
+ assert_selected_and_displayed(2);
+
+ await switch_tab(tab2);
+ assert_nothing_selected();
+ select_click_row(3);
+
+ await switch_tab(tab1);
+ assert_selected_and_displayed(2);
+ select_shift_click_row(4); // 2-4 selected
+ assert_selected_and_displayed([2, 4]); // ensures multi-message summary
+
+ await switch_tab(tab2);
+ assert_selected_and_displayed(3);
+
+ close_tab(tab2);
+ assert_selected_and_displayed([2, 4]);
+});
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1850190
+/**
+ * Verify that we scroll to new messages when we enter a folder.
+ */
+add_task(async function test_enter_scroll_to_new() {
+ // This should be the default anyway:
+ Services.prefs.setBoolPref("mailnews.scroll_to_new_message", true);
+ await be_in_folder(folder);
+ get_about_3pane().sortController.sortAscending();
+ await select_click_row(1);
+ await enter_folder(folder2);
+ // When a folder is switched to, a new message should be visible and
+ // selections are not restored:
+ await make_message_sets_in_folders([folder], [{ count: 1 }]);
+ await enter_folder(folder);
+ await assert_visible(-1);
+ await assert_nothing_selected();
+});
+
+/**
+ * Test that the last selected message persists through folder changes.
+ */
+add_task(async function test_selection_persists_through_folder_changes() {
+ // be in the folder
+ await be_in_folder(folder);
+ // select a message
+ select_click_row(3);
+ // leave and re-enter the folder
+ await enter_folder(folder.rootFolder);
+ await enter_folder(folder);
+ // make sure it is selected and displayed
+ assert_selected_and_displayed(3);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_summarization.js b/comm/mail/test/browser/folder-display/browser_summarization.js
new file mode 100644
index 0000000000..6861a7e605
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_summarization.js
@@ -0,0 +1,462 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that summarization happens at the right time, that it clears itself at
+ * the right time, that it waits for selection stability when recently
+ * summarized, and that summarization does not break under tabbing.
+ *
+ * Because most of the legwork is done automatically by
+ * test-folder-display-helpers, the more basic tests may look like general
+ * selection / tabbing tests, but are intended to specifically exercise the
+ * summarization logic and edge cases. (Although general selection tests and
+ * tab tests may do the same thing too...)
+ *
+ * Things we don't test but should:
+ * - The difference between thread summary and multi-message summary.
+ */
+
+"use strict";
+
+var { ensure_card_exists, ensure_no_card_exists } = ChromeUtils.import(
+ "resource://testing-common/mozmill/AddressBookHelpers.jsm"
+);
+var {
+ add_message_sets_to_folders,
+ assert_collapsed,
+ assert_expanded,
+ assert_messages_summarized,
+ assert_message_not_in_view,
+ assert_nothing_selected,
+ assert_selected,
+ assert_selected_and_displayed,
+ assert_summary_contains_N_elts,
+ be_in_folder,
+ close_tab,
+ collapse_all_threads,
+ create_folder,
+ create_thread,
+ create_virtual_folder,
+ make_display_threaded,
+ make_display_unthreaded,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_tab,
+ open_selected_message_in_new_tab,
+ plan_to_wait_for_folder_events,
+ select_click_row,
+ select_control_click_row,
+ select_none,
+ select_shift_click_row,
+ switch_tab,
+ toggle_thread_row,
+ wait_for_blank_content_pane,
+ wait_for_folder_events,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var folder;
+var thread1, thread2, msg1, msg2;
+
+add_setup(async function () {
+ // Make sure the whole test starts with an unthreaded view in all folders.
+ Services.prefs.setIntPref("mailnews.default_view_flags", 0);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("mailnews.default_view_flags");
+ });
+
+ folder = await create_folder("SummarizationA");
+ thread1 = create_thread(10);
+ msg1 = create_thread(1);
+ thread2 = create_thread(10);
+ msg2 = create_thread(1);
+ await add_message_sets_to_folders([folder], [thread1, msg1, thread2, msg2]);
+});
+
+add_task(async function test_basic_summarization() {
+ await be_in_folder(folder);
+
+ // - make sure we get a summary
+ select_click_row(0);
+ select_shift_click_row(5);
+ // this will verify a multi-message display is happening
+ assert_selected_and_displayed([0, 5]);
+});
+
+add_task(function test_summarization_goes_away() {
+ select_none();
+ assert_nothing_selected();
+});
+
+/**
+ * Verify that we update summarization when switching amongst tabs.
+ */
+add_task(async function test_folder_tabs_update_correctly() {
+ // tab with summary
+ let tabA = await be_in_folder(folder);
+ select_click_row(0);
+ select_control_click_row(2);
+ assert_selected_and_displayed(0, 2);
+
+ // tab with nothing
+ let tabB = await open_folder_in_new_tab(folder);
+ wait_for_blank_content_pane();
+ assert_nothing_selected();
+
+ // correct changes, none <=> summary
+ await switch_tab(tabA);
+ assert_selected_and_displayed(0, 2);
+ await switch_tab(tabB);
+ assert_nothing_selected();
+
+ // correct changes, one <=> summary
+ select_click_row(0);
+ assert_selected_and_displayed(0);
+ await switch_tab(tabA);
+ assert_selected_and_displayed(0, 2);
+ await switch_tab(tabB);
+ assert_selected_and_displayed(0);
+
+ // correct changes, summary <=> summary
+ select_shift_click_row(3);
+ assert_selected_and_displayed([0, 3]);
+ await switch_tab(tabA);
+ assert_selected_and_displayed(0, 2);
+ await switch_tab(tabB);
+ assert_selected_and_displayed([0, 3]);
+
+ // closing tab returns state correctly...
+ close_tab(tabB);
+ assert_selected_and_displayed(0, 2);
+});
+
+add_task(async function test_message_tabs_update_correctly() {
+ let tabFolder = await be_in_folder(folder);
+ let message = select_click_row(0);
+ assert_selected_and_displayed(0);
+
+ let tabMessage = await open_selected_message_in_new_tab();
+ assert_selected_and_displayed(message);
+
+ await switch_tab(tabFolder);
+ select_shift_click_row(2);
+ assert_selected_and_displayed([0, 2]);
+
+ await switch_tab(tabMessage);
+ assert_selected_and_displayed(message);
+
+ await switch_tab(tabFolder);
+ assert_selected_and_displayed([0, 2]);
+
+ close_tab(tabMessage);
+});
+
+/**
+ * Test the stabilization logic by making the stabilization interval absurd and
+ * then manually clearing things up.
+ */
+add_task(async function test_selection_stabilization_logic() {
+ // make sure all summarization has run to completion.
+ await new Promise(resolve => setTimeout(resolve));
+ // does not summarize anything, does not affect timer
+ select_click_row(0);
+ // does summarize things. timer will be tick tick ticking!
+ select_shift_click_row(1);
+ // verify that things were summarized...
+ assert_selected_and_displayed([0, 1]);
+ // save the set of messages so we can verify the summary sticks to this.
+ let messages = mc.window.gFolderDisplay.selectedMessages;
+
+ // make sure the
+
+ // this will not summarize!
+ select_shift_click_row(2, mc, true);
+ // verify that our summary is still just 0 and 1.
+ assert_messages_summarized(mc, messages);
+
+ // - pretend the timer fired.
+ // we need to de-schedule the timer, but do not need to clear the variable
+ // because it will just get overwritten anyways
+ mc.window.clearTimeout(mc.messageDisplay._summaryStabilityTimeout);
+ mc.messageDisplay._showSummary(true);
+
+ // - the summary should now be up-to-date
+ assert_selected_and_displayed([0, 2]);
+});
+
+add_task(function test_summarization_thread_detection() {
+ select_none();
+ assert_nothing_selected();
+ make_display_threaded();
+ select_click_row(0);
+ select_shift_click_row(9);
+ let messages = mc.window.gFolderDisplay.selectedMessages;
+ toggle_thread_row(0);
+ assert_messages_summarized(mc, messages);
+ // count the number of messages represented
+ assert_summary_contains_N_elts("#message_list > li", 10);
+ select_shift_click_row(1);
+ // this should have shifted to the multi-message view
+ assert_summary_contains_N_elts(".item_header > .date", 0);
+ assert_summary_contains_N_elts(".item_header > .subject", 2);
+ select_none();
+ assert_nothing_selected();
+ select_click_row(1); // select a single message
+ select_shift_click_row(2); // add a thread
+ assert_summary_contains_N_elts(".item_header > .date", 0);
+ assert_summary_contains_N_elts(".item_header > .subject", 2);
+});
+
+/**
+ * If you are looking at a message that becomes part of a thread because of the
+ * arrival of a new message, expand the thread so you do not have the message
+ * turn into a summary beneath your feet.
+ *
+ * There are really two cases here:
+ * - The thread gets moved because its sorted position changes.
+ * - The thread does not move.
+ */
+add_task(async function test_new_thread_that_was_not_summarized_expands() {
+ await be_in_folder(folder);
+ make_display_threaded();
+ // - create the base messages
+ let [willMoveMsg, willNotMoveMsg] = await make_message_sets_in_folders(
+ [folder],
+ [{ count: 1 }, { count: 1 }]
+ );
+
+ // - do the non-move case
+ // XXX actually, this still gets treated as a move. I don't know why...
+ // select it
+ select_click_row(willNotMoveMsg);
+ assert_selected_and_displayed(willNotMoveMsg);
+
+ // give it a friend...
+ await make_message_sets_in_folders(
+ [folder],
+ [{ count: 1, inReplyTo: willNotMoveMsg }]
+ );
+ assert_expanded(willNotMoveMsg);
+ assert_selected_and_displayed(willNotMoveMsg);
+
+ // - do the move case
+ select_click_row(willMoveMsg);
+ assert_selected_and_displayed(willMoveMsg);
+
+ // give it a friend...
+ await make_message_sets_in_folders(
+ [folder],
+ [{ count: 1, inReplyTo: willMoveMsg }]
+ );
+ assert_expanded(willMoveMsg);
+ assert_selected_and_displayed(willMoveMsg);
+});
+
+/**
+ * Selecting an existing (and collapsed) thread, then add a message and make
+ * sure the summary updates.
+ */
+add_task(
+ async function test_summary_updates_when_new_message_added_to_collapsed_thread() {
+ await be_in_folder(folder);
+ make_display_threaded();
+ collapse_all_threads();
+
+ // - select the thread root, thereby summarizing it
+ let thread1Root = select_click_row(thread1); // this just uses the root msg
+ assert_collapsed(thread1Root);
+ // just the thread root should be selected
+ assert_selected(thread1Root);
+ // but the whole thread should be summarized
+ assert_messages_summarized(mc, thread1);
+
+ // - add a new message, make sure it's in the summary now.
+ let [thread1Extra] = await make_message_sets_in_folders(
+ [folder],
+ [{ count: 1, inReplyTo: thread1 }]
+ );
+ let thread1All = thread1.union(thread1Extra);
+ assert_selected(thread1Root);
+ assert_messages_summarized(mc, thread1All);
+ }
+);
+
+add_task(async function test_summary_when_multiple_identities() {
+ // First half of the test, makes sure messageDisplay.js understands there's
+ // only one thread
+ let folder1 = await create_folder("Search1");
+ await be_in_folder(folder1);
+ let thread1 = create_thread(1);
+ await add_message_sets_to_folders([folder1], [thread1]);
+
+ let folder2 = await create_folder("Search2");
+ await be_in_folder(folder2);
+ await make_message_sets_in_folders(
+ [folder2],
+ [{ count: 1, inReplyTo: thread1 }]
+ );
+
+ let folderVirtual = create_virtual_folder(
+ [folder1, folder2],
+ {},
+ true,
+ "SearchBoth"
+ );
+
+ // Do the needed tricks
+ await be_in_folder(folder1);
+ select_click_row(0);
+ plan_to_wait_for_folder_events(
+ "DeleteOrMoveMsgCompleted",
+ "DeleteOrMoveMsgFailed"
+ );
+ mc.window.MsgMoveMessage(folder2);
+ wait_for_folder_events();
+
+ await be_in_folder(folder2);
+ select_click_row(1);
+ plan_to_wait_for_folder_events(
+ "DeleteOrMoveMsgCompleted",
+ "DeleteOrMoveMsgFailed"
+ );
+ mc.window.MsgMoveMessage(folder1);
+ wait_for_folder_events();
+
+ await be_in_folder(folderVirtual);
+ make_display_threaded();
+ collapse_all_threads();
+
+ // Assertions
+ select_click_row(0);
+ assert_messages_summarized(mc, mc.window.gFolderDisplay.selectedMessages);
+ // Thread summary shows a date, while multimessage summary shows a subject.
+ assert_summary_contains_N_elts(".item_header > .subject", 0);
+ assert_summary_contains_N_elts(".item_header > .date", 2);
+
+ // Second half of the test, makes sure MultiMessageSummary groups messages
+ // according to their view thread id
+ thread1 = create_thread(1);
+ await add_message_sets_to_folders([folder1], [thread1]);
+ await be_in_folder(folderVirtual);
+ select_shift_click_row(1);
+
+ assert_summary_contains_N_elts(".item_header > .subject", 2);
+});
+
+function extract_first_address(thread) {
+ let addresses = MailServices.headerParser.parseEncodedHeader(
+ thread1.getMsgHdr(0).mime2DecodedAuthor
+ );
+ return addresses[0];
+}
+
+function check_address_name(name) {
+ let htmlframe = mc.window.document.getElementById("multimessage");
+ let match = htmlframe.contentDocument.querySelector(".author");
+ if (match.textContent != name) {
+ throw new Error(
+ "Expected to find sender named '" +
+ name +
+ "', found '" +
+ match.textContent +
+ "'"
+ );
+ }
+}
+
+add_task(async function test_display_name_no_abook() {
+ await be_in_folder(folder);
+
+ let address = extract_first_address(thread1);
+ ensure_no_card_exists(address.email);
+
+ collapse_all_threads();
+ select_click_row(thread1);
+
+ // No address book entry, we display name and e-mail address.
+ check_address_name(address.name + " <" + address.email + ">");
+});
+
+add_task(async function test_display_name_abook() {
+ await be_in_folder(folder);
+
+ let address = extract_first_address(thread1);
+ ensure_card_exists(address.email, "My Friend", true);
+
+ collapse_all_threads();
+ select_click_row(thread1);
+
+ check_address_name("My Friend");
+});
+
+add_task(async function test_display_name_abook_no_pdn() {
+ await be_in_folder(folder);
+
+ let address = extract_first_address(thread1);
+ ensure_card_exists(address.email, "My Friend", false);
+
+ collapse_all_threads();
+ select_click_row(thread1);
+
+ // With address book entry but display name not preferred, we display name and
+ // e-mail address.
+ check_address_name(address.name + " <" + address.email + ">");
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
+
+add_task(async function test_archive_and_delete_messages() {
+ await be_in_folder(folder);
+ select_none();
+ assert_nothing_selected();
+ make_display_unthreaded();
+ select_click_row(0);
+ select_shift_click_row(2);
+ let messages = mc.window.gFolderDisplay.selectedMessages;
+
+ let contentWindow =
+ mc.window.document.getElementById("multimessage").contentWindow;
+ // Archive selected messages.
+ plan_to_wait_for_folder_events(
+ "DeleteOrMoveMsgCompleted",
+ "DeleteOrMoveMsgFailed"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ contentWindow.document.getElementById("hdrArchiveButton"),
+ {},
+ contentWindow
+ );
+
+ wait_for_folder_events();
+ assert_message_not_in_view(messages);
+
+ select_none();
+ assert_nothing_selected();
+ select_click_row(0);
+ select_shift_click_row(2);
+ messages = mc.window.gFolderDisplay.selectedMessages;
+
+ // Delete selected messages.
+ plan_to_wait_for_folder_events(
+ "DeleteOrMoveMsgCompleted",
+ "DeleteOrMoveMsgFailed"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ contentWindow.document.getElementById("hdrTrashButton"),
+ {},
+ contentWindow
+ );
+ wait_for_folder_events();
+ assert_message_not_in_view(messages);
+});
diff --git a/comm/mail/test/browser/folder-display/browser_syntheticViews.js b/comm/mail/test/browser/folder-display/browser_syntheticViews.js
new file mode 100644
index 0000000000..4e114531cd
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_syntheticViews.js
@@ -0,0 +1,292 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ add_message_sets_to_folders,
+ be_in_folder,
+ create_folder,
+ create_thread,
+ delete_messages,
+ inboxFolder,
+ mc,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+const { SyntheticPartLeaf } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+const { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+
+const { GlodaMsgIndexer } = ChromeUtils.import(
+ "resource:///modules/gloda/IndexMsg.jsm"
+);
+
+add_setup(async function () {
+ Services.prefs.setBoolPref("mailnews.mark_message_read.auto", false);
+ Services.prefs.setBoolPref("mailnews.start_page.enabled", false);
+ Services.prefs.setIntPref("mailnews.default_view_flags", 0);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("mailnews.mark_message_read.auto");
+ Services.prefs.clearUserPref("mailnews.start_page.enabled");
+ Services.prefs.clearUserPref("mailnews.default_view_flags");
+ });
+});
+
+function createThreadWithTerm(msgCount, term) {
+ let thread = create_thread(msgCount);
+ for (let msg of thread.synMessages) {
+ msg.bodyPart = new SyntheticPartLeaf(term);
+ }
+ return thread;
+}
+
+async function waitForThreadIndexed(thread) {
+ let dbView = window.gFolderDisplay.view.dbView;
+ await TestUtils.waitForCondition(
+ () =>
+ thread.synMessages.every((_, i) =>
+ window.Gloda.isMessageIndexed(dbView.getMsgHdrAt(i))
+ ),
+ "Messages were not indexed in time"
+ );
+}
+
+function doGlobalSearch(term) {
+ let searchInput = window.document.querySelector("#searchInput");
+ searchInput.value = term;
+ EventUtils.synthesizeMouseAtCenter(searchInput, {}, window);
+ EventUtils.synthesizeKey("VK_RETURN", {}, window);
+}
+
+async function clickShowResultsAsList(tab) {
+ let iframe = tab.querySelector("iframe");
+ await BrowserTestUtils.waitForEvent(iframe.contentWindow, "load");
+
+ let browser = iframe.contentDocument.querySelector("browser");
+ await TestUtils.waitForCondition(
+ () =>
+ browser.contentWindow.FacetContext &&
+ browser.contentWindow.FacetContext.rootWin != null,
+ "reachOutAndTouchFrame() did not run in time"
+ );
+
+ let anchor = browser.contentDocument.querySelector("#gloda-showall");
+ anchor.click();
+}
+
+async function clickMarkRead(row, col) {
+ await openContextMenu(row, col);
+ await clickSubMenuItem("#mailContext-mark", "#mailContext-markRead");
+}
+
+async function clickMarkThreadAsRead(row, col) {
+ await openContextMenu(row, col);
+ await clickSubMenuItem("#mailContext-mark", "#mailContext-markThreadAsRead");
+}
+
+async function clickSubMenuItem(menuId, itemId) {
+ let menu = window.document.querySelector(menuId);
+ let item = menu.querySelector(itemId);
+
+ let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ menu.openMenu(true);
+ await shownPromise;
+
+ let hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.menupopup.activateItem(item);
+ await hiddenPromise;
+}
+
+async function openConversationView(row, col) {
+ let menu = window.document.querySelector("#mailContext");
+ let item = window.document.querySelector("#mailContext-openConversation");
+ let prevTab = window.document.getElementById("tabmail").selectedTab;
+
+ let loadedPromise = BrowserTestUtils.waitForEvent(window, "MsgsLoaded");
+ await openContextMenu(row, col);
+ menu.activateItem(item);
+ await loadedPromise;
+ await TestUtils.waitForCondition(
+ () => window.document.getElementById("tabmail").selectedTab != prevTab,
+ "Conversation View tab did not open"
+ );
+}
+
+async function openContextMenu(row, column) {
+ let menu = window.document.getElementById("mailContext");
+ let tree = window.document.getElementById("threadTree");
+
+ let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ mailTestUtils.treeClick(EventUtils, window, tree, row, column, {});
+ mailTestUtils.treeClick(EventUtils, window, tree, row, column, {
+ type: "contextmenu",
+ });
+ await shownPromise;
+}
+
+function closeTabs() {
+ let tabmail = document.querySelector("tabmail");
+ while (tabmail.tabInfo.length > 1) {
+ tabmail.closeTab(1);
+ }
+}
+
+/**
+ * Test we can mark a message as read in the list view version of the global
+ * search results.
+ */
+add_task(async function testListViewMarkRead() {
+ let folder = await create_folder("ListViewMarkReadFolder");
+ let term = "listviewmarkread";
+ let thread = createThreadWithTerm(2, term);
+
+ registerCleanupFunction(async () => {
+ await be_in_folder(inboxFolder);
+ await delete_messages(thread);
+
+ let trash = folder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ folder.deleteSelf(null);
+ trash.emptyTrash(null);
+ });
+
+ await be_in_folder(folder);
+ await add_message_sets_to_folders([folder], [thread]);
+
+ await new Promise(callback => {
+ GlodaMsgIndexer.indexFolder(folder, { callback, force: true });
+ });
+
+ await waitForThreadIndexed(thread);
+ doGlobalSearch(term);
+
+ let tab = document.querySelector(
+ "tabmail>tabbox>tabpanels>vbox[selected=true]"
+ );
+ await clickShowResultsAsList(tab);
+ await clickMarkRead(0, 4);
+
+ let dbView = window.gFolderDisplay.view.dbView;
+ Assert.ok(dbView.getMsgHdrAt(0).isRead, "Message 0 is read");
+ Assert.ok(!dbView.getMsgHdrAt(1).isRead, "Message 1 is not read");
+
+ closeTabs();
+});
+
+/**
+ * Test we can mark a thread as read in the list view version of the global
+ * search results.
+ */
+add_task(async function testListViewMarkThreadAsRead() {
+ let folder = await create_folder("ListViewMarkThreadAsReadFolder");
+ let term = "listviewmarkthreadasread ";
+ let thread = createThreadWithTerm(3, term);
+
+ registerCleanupFunction(async () => {
+ await be_in_folder(inboxFolder);
+ await delete_messages(thread);
+ let trash = folder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ folder.deleteSelf(null);
+ trash.emptyTrash(null);
+ });
+
+ await be_in_folder(folder);
+ await add_message_sets_to_folders([folder], [thread]);
+
+ await new Promise(callback => {
+ GlodaMsgIndexer.indexFolder(folder, { callback, force: true });
+ });
+
+ await waitForThreadIndexed(thread);
+ doGlobalSearch(term);
+
+ let tab = document.querySelector(
+ "tabmail>tabbox>tabpanels>vbox[selected=true]"
+ );
+ await clickShowResultsAsList(tab);
+ await clickMarkThreadAsRead(0, 4);
+
+ let dbView = window.gFolderDisplay.view.dbView;
+ thread.synMessages.forEach((_, i) => {
+ Assert.ok(dbView.getMsgHdrAt(i).isRead, `Message ${i} is read`);
+ });
+
+ closeTabs();
+});
+
+/**
+ * Test we can mark a message as read in a conversation view.
+ */
+add_task(async function testConversationViewMarkRead() {
+ let folder = await create_folder("ConversationViewMarkReadFolder");
+ let thread = create_thread(2);
+
+ registerCleanupFunction(async () => {
+ await be_in_folder(inboxFolder);
+ await delete_messages(thread);
+
+ let trash = folder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ folder.deleteSelf(null);
+ trash.emptyTrash(null);
+ });
+
+ await be_in_folder(folder);
+ await add_message_sets_to_folders([folder], [thread]);
+
+ await new Promise(callback => {
+ GlodaMsgIndexer.indexFolder(folder, {
+ callback,
+ force: true,
+ });
+ });
+
+ await waitForThreadIndexed(thread);
+ await openConversationView(1, 1);
+ await clickMarkRead(0, 4);
+
+ let dbView = window.gFolderDisplay.view.dbView;
+ Assert.ok(dbView.getMsgHdrAt(0).isRead, "Message 0 is read");
+ Assert.ok(!dbView.getMsgHdrAt(1).isRead, "Message 1 is not read");
+
+ closeTabs();
+});
+
+/**
+ * Test we can mark a thread as read in a conversation view.
+ */
+add_task(async function testConversationViewMarkThreadAsRead() {
+ let folder = await create_folder("ConversationViewMarkThreadAsReadFolder");
+ let thread = create_thread(3);
+
+ registerCleanupFunction(async () => {
+ await be_in_folder(inboxFolder);
+ await delete_messages(thread);
+
+ let trash = folder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ folder.deleteSelf(null);
+ trash.emptyTrash(null);
+ });
+
+ await be_in_folder(folder);
+ await add_message_sets_to_folders([folder], [thread]);
+
+ await new Promise(callback => {
+ GlodaMsgIndexer.indexFolder(folder, { callback, force: true });
+ });
+
+ await waitForThreadIndexed(thread);
+ await openConversationView(1, 1);
+ await clickMarkThreadAsRead(0, 4);
+
+ let dbView = window.gFolderDisplay.view.dbView;
+ thread.synMessages.forEach((_, i) => {
+ Assert.ok(dbView.getMsgHdrAt(i).isRead, `Message ${i} is read.`);
+ });
+
+ closeTabs();
+});
diff --git a/comm/mail/test/browser/folder-display/browser_tabsSimple.js b/comm/mail/test/browser/folder-display/browser_tabsSimple.js
new file mode 100644
index 0000000000..d0a514c616
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_tabsSimple.js
@@ -0,0 +1,195 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that opening new folder and message tabs has the expected result and
+ * that closing them doesn't break anything. sid0 added checks for focus
+ * transitions at one point; I (asuth) am changing our test infrastructure to
+ * cause more realistic focus changes so those changes now look sillier
+ * because in many cases we are explicitly setting focus back after the thread
+ * tree gains focus.
+ */
+
+"use strict";
+
+var {
+ assert_folder_tree_focused,
+ assert_message_pane_focused,
+ assert_messages_in_view,
+ assert_nothing_selected,
+ assert_selected_and_displayed,
+ assert_thread_tree_focused,
+ be_in_folder,
+ close_tab,
+ create_folder,
+ focus_folder_tree,
+ focus_message_pane,
+ focus_thread_tree,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_tab,
+ open_selected_message_in_new_tab,
+ select_click_row,
+ switch_tab,
+ wait_for_blank_content_pane,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var folderA, folderB, setA, setB;
+
+add_setup(async function () {
+ folderA = await create_folder("TabsSimpleA");
+ folderB = await create_folder("TabsSimpleB");
+
+ // We will verify we are seeing the right folder by checking that it has the
+ // right messages in it.
+ [setA] = await make_message_sets_in_folders([folderA], [{}]);
+ [setB] = await make_message_sets_in_folders([folderB], [{}]);
+});
+
+/** The tabs in our test. */
+var tabFolderA, tabFolderB, tabMessageA, tabMessageB;
+/** The message that we selected for tab display, to check it worked right. */
+var messageA, messageB;
+
+/**
+ * Make sure the default tab works right.
+ */
+add_task(async function test_open_folder_a() {
+ tabFolderA = await be_in_folder(folderA);
+ assert_messages_in_view(setA);
+ assert_nothing_selected();
+ // Focus the folder tree here
+ focus_folder_tree();
+});
+
+/**
+ * Open tab b, make sure it works right.
+ */
+add_task(async function test_open_folder_b_in_tab() {
+ tabFolderB = await open_folder_in_new_tab(folderB);
+ wait_for_blank_content_pane();
+ assert_messages_in_view(setB);
+ assert_nothing_selected();
+ focus_thread_tree();
+});
+
+/**
+ * Go back to tab/folder A and make sure we change correctly.
+ */
+add_task(async function test_switch_to_tab_folder_a() {
+ await switch_tab(tabFolderA);
+ assert_messages_in_view(setA);
+ assert_nothing_selected();
+ assert_folder_tree_focused();
+});
+
+/**
+ * Select a message in folder A and open it in a new window, making sure that
+ * the displayed message is the right one.
+ */
+add_task(async function test_open_message_a_in_tab() {
+ // (this focuses the thread tree for tabFolderA...)
+ messageA = select_click_row(0);
+ // (...refocus the folder tree for our sticky check below)
+ focus_folder_tree();
+ tabMessageA = await open_selected_message_in_new_tab();
+ assert_selected_and_displayed(messageA);
+ assert_message_pane_focused();
+});
+
+/**
+ * Go back to tab/folder B and make sure we change correctly.
+ */
+add_task(async function test_switch_to_tab_folder_b() {
+ await switch_tab(tabFolderB);
+ assert_messages_in_view(setB);
+ assert_nothing_selected();
+ assert_thread_tree_focused();
+});
+
+/**
+ * Select a message in folder B and open it in a new window, making sure that
+ * the displayed message is the right one.
+ */
+add_task(async function test_open_message_b_in_tab() {
+ messageB = select_click_row(0);
+ // Let's focus the message pane now
+ focus_message_pane();
+ tabMessageB = await open_selected_message_in_new_tab();
+ assert_selected_and_displayed(messageB);
+ assert_message_pane_focused();
+});
+
+/**
+ * Switch to message tab A.
+ */
+add_task(async function test_switch_to_message_a() {
+ await switch_tab(tabMessageA);
+ assert_selected_and_displayed(messageA);
+ assert_message_pane_focused();
+});
+
+/**
+ * Close message tab A (when it's in the foreground).
+ */
+add_task(function test_close_message_a() {
+ close_tab();
+ // our current tab is now undefined for the purposes of this test.
+});
+
+/**
+ * Make sure all the other tabs are still happy.
+ */
+add_task(async function test_tabs_are_still_happy() {
+ await switch_tab(tabFolderB);
+ assert_messages_in_view(setB);
+ assert_selected_and_displayed(messageB);
+ assert_message_pane_focused();
+
+ await switch_tab(tabMessageB);
+ assert_selected_and_displayed(messageB);
+ assert_message_pane_focused();
+
+ await switch_tab(tabFolderA);
+ assert_messages_in_view(setA);
+ assert_selected_and_displayed(messageA);
+ // focus restoration uses setTimeout(0) and so we need to give it a chance
+ await new Promise(resolve => setTimeout(resolve));
+ assert_folder_tree_focused();
+});
+
+/**
+ * Close message tab B (when it's in the background).
+ */
+add_task(function test_close_message_b() {
+ close_tab(tabMessageB);
+ // we should still be on folder A
+ assert_messages_in_view(setA);
+ assert_selected_and_displayed(messageA);
+ assert_folder_tree_focused();
+});
+
+/**
+ * Switch to tab B, close it, make sure we end up on tab A.
+ */
+add_task(async function test_close_folder_b() {
+ await switch_tab(tabFolderB);
+ assert_messages_in_view(setB);
+ assert_selected_and_displayed(messageB);
+ assert_message_pane_focused();
+
+ close_tab();
+ assert_messages_in_view(setA);
+ assert_selected_and_displayed(messageA);
+ assert_folder_tree_focused();
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_viewSource.js b/comm/mail/test/browser/folder-display/browser_viewSource.js
new file mode 100644
index 0000000000..63ce81daa3
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_viewSource.js
@@ -0,0 +1,222 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that view-source content can be reloaded to change encoding.
+ */
+
+"use strict";
+
+var utils = ChromeUtils.import("resource://testing-common/mozmill/utils.jsm");
+var { be_in_folder, create_folder, get_about_message, mc, select_click_row } =
+ ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+ );
+var {
+ click_menus_in_sequence,
+ close_window,
+ plan_for_new_window,
+ wait_for_new_window,
+} = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+
+var folder;
+
+// Message content as stored in the message folder. Non-ASCII characters as
+// escape codes for clarity.
+var contentLatin1 = "Testar, ett tv\xE5 tre.";
+var contentUTF8 = "Testar, ett tv\xC3\xA5 tre.";
+// Message content as it should be displayed to the user.
+var contentReadable = "Testar, ett två tre.";
+// UTF-8 content displayed as Latin1.
+var contentGarbled = "Testar, ett två tre.";
+// Latin1 content displayed as UTF-8.
+var contentReplaced = "Testar, ett tv� tre.";
+
+add_setup(async function () {
+ folder = await create_folder("viewsource");
+ addToFolder("ISO-8859-1 header/ISO-8859-1 body", "ISO-8859-1", contentLatin1);
+ addToFolder("ISO-8859-1 header/UTF-8 body", "ISO-8859-1", contentUTF8);
+ addToFolder("UTF-8 header/ISO-8859-1 body", "UTF-8", contentLatin1);
+ addToFolder("UTF-8 header/UTF-8 body", "UTF-8", contentUTF8);
+
+ await be_in_folder(folder);
+});
+
+registerCleanupFunction(() => {
+ folder.deleteSelf(null);
+});
+
+/** Header matches the body. Should be readable in both places. */
+add_task(async function latin1Header_with_latin1Body() {
+ await subtest(0, contentReadable, contentReadable);
+});
+/** Header doesn't match the body. Unicode characters should be displayed. */
+add_task(async function latin1Header_with_utf8Body() {
+ await subtest(1, contentGarbled, contentGarbled);
+});
+/**
+ * Header doesn't match the body. Unreadable characters should be replaced
+ * in both places, but the view-source display defaults to windows-1252.
+ */
+add_task(async function utf8Header_with_latin1Body() {
+ await subtest(2, contentReplaced, contentReadable);
+});
+/**
+ * Header matches the body. Should be readable in both places, but the
+ * view-source display defaults to windows-1252.
+ */
+add_task(async function utf8Header_with_utf8Body() {
+ await subtest(3, contentReadable, contentGarbled);
+});
+
+function addToFolder(subject, charset, body) {
+ let msgId = Services.uuid.generateUUID() + "@invalid";
+
+ let source =
+ "From - Sat Nov 1 12:39:54 2008\n" +
+ "X-Mozilla-Status: 0001\n" +
+ "X-Mozilla-Status2: 00000000\n" +
+ "Message-ID: <" +
+ msgId +
+ ">\n" +
+ "Date: Wed, 11 Jun 2008 20:32:02 -0400\n" +
+ "From: Tester <tests@mozillamessaging.invalid>\n" +
+ "MIME-Version: 1.0\n" +
+ "To: anna@example.com\n" +
+ `Subject: ${subject}` +
+ "\n" +
+ `Content-Type: text/plain; charset=${charset}\n` +
+ "Content-Transfer-Encoding: 8bit\n" +
+ "\n" +
+ body +
+ "\n";
+
+ folder.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ folder.addMessage(source);
+
+ return folder.msgDatabase.getMsgHdrForMessageID(msgId);
+}
+
+async function subtest(row, expectedDisplayed, expectedSource) {
+ select_click_row(row);
+
+ let aboutMessage = get_about_message();
+ let displayContent =
+ aboutMessage.getMessagePaneBrowser().contentDocument.body.textContent;
+ Assert.stringContains(
+ displayContent,
+ expectedDisplayed,
+ "Message content must include the readable text"
+ );
+ Assert.equal(
+ aboutMessage.document.getElementById("messagepane").docShell.charset,
+ "UTF-8"
+ );
+
+ plan_for_new_window("navigator:view-source");
+ EventUtils.synthesizeKey("U", { shiftKey: false, accelKey: true });
+ let viewSourceController = wait_for_new_window("navigator:view-source");
+
+ utils.waitFor(
+ () =>
+ viewSourceController.window.document
+ .getElementById("content")
+ .contentDocument.querySelector("pre") != null,
+ "Timeout waiting for the latin1 view-source document to load."
+ );
+
+ let source =
+ viewSourceController.window.document.getElementById("content")
+ .contentDocument.body.textContent;
+ Assert.stringContains(
+ source,
+ expectedSource,
+ "View source must contain the readable text"
+ );
+
+ let popupshown;
+
+ // We can't use the menu on macOS.
+ if (AppConstants.platform != "macosx") {
+ let theContent =
+ viewSourceController.window.document.getElementById("content");
+ // Keep a reference to the originally loaded document.
+ let doc = theContent.contentDocument;
+
+ // Click the new window to make it receive further events properly.
+ EventUtils.synthesizeMouseAtCenter(theContent, {}, theContent.ownerGlobal);
+ await new Promise(resolve => setTimeout(resolve));
+
+ popupshown = BrowserTestUtils.waitForEvent(
+ viewSourceController.window.document.getElementById("viewmenu-popup"),
+ "popupshown"
+ );
+ let menuView =
+ viewSourceController.window.document.getElementById("menu_view");
+ EventUtils.synthesizeMouseAtCenter(menuView, {}, menuView.ownerGlobal);
+ await popupshown;
+
+ Assert.equal(
+ viewSourceController.window.document.getElementById(
+ "repair-text-encoding"
+ ).disabled,
+ expectedSource == contentReadable
+ );
+
+ await click_menus_in_sequence(
+ viewSourceController.window.document.getElementById("viewmenu-popup"),
+ [{ id: "repair-text-encoding" }]
+ );
+
+ if (expectedSource != contentReadable) {
+ utils.waitFor(
+ () =>
+ viewSourceController.window.document.getElementById("content")
+ .contentDocument != doc &&
+ viewSourceController.window.document
+ .getElementById("content")
+ .contentDocument.querySelector("pre") != null,
+ "Timeout waiting utf-8 encoded view-source document to load."
+ );
+
+ source =
+ viewSourceController.window.document.getElementById("content")
+ .contentDocument.body.textContent;
+ Assert.stringContains(
+ source,
+ contentReadable,
+ "View source must contain the readable text"
+ );
+ }
+ }
+
+ // Check the context menu while were here.
+ let browser = viewSourceController.window.document.getElementById("content");
+ let contextMenu = viewSourceController.window.document.getElementById(
+ "viewSourceContextMenu"
+ );
+ popupshown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "body",
+ { type: "contextmenu" },
+ browser
+ );
+ await popupshown;
+
+ let actualItems = [];
+ for (let item of contextMenu.children) {
+ if (item.localName == "menuitem" && !item.hidden) {
+ actualItems.push(item.id);
+ }
+ }
+ Assert.deepEqual(actualItems, [
+ "cMenu_copy",
+ "cMenu_selectAll",
+ "cMenu_find",
+ "cMenu_findAgain",
+ ]);
+ contextMenu.hidePopup();
+
+ close_window(viewSourceController);
+}
diff --git a/comm/mail/test/browser/folder-display/browser_virtualFolderCommands.js b/comm/mail/test/browser/folder-display/browser_virtualFolderCommands.js
new file mode 100644
index 0000000000..39ca926877
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_virtualFolderCommands.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that commands on virtual folders work properly.
+ */
+
+"use strict";
+
+var {
+ be_in_folder,
+ create_folder,
+ create_virtual_folder,
+ expand_all_threads,
+ get_about_3pane,
+ make_display_threaded,
+ make_message_sets_in_folders,
+ mc,
+ select_click_row,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var msgsPerThread = 5;
+var singleVirtFolder;
+var multiVirtFolder;
+
+add_setup(async function () {
+ let folderOne = await create_folder();
+ let folderTwo = await create_folder();
+ await make_message_sets_in_folders([folderOne], [{ msgsPerThread }]);
+ await make_message_sets_in_folders([folderTwo], [{ msgsPerThread }]);
+
+ singleVirtFolder = create_virtual_folder([folderOne], {});
+ multiVirtFolder = create_virtual_folder([folderOne, folderTwo], {});
+});
+
+add_task(async function test_single_folder_select_thread() {
+ await be_in_folder(singleVirtFolder);
+ let win = get_about_3pane();
+ make_display_threaded();
+ expand_all_threads();
+
+ // Try selecting the thread from the root message.
+ select_click_row(0);
+ EventUtils.synthesizeKey("a", { accelKey: true, shiftKey: true });
+ Assert.ok(
+ win.gDBView.selection.count == msgsPerThread,
+ "Didn't select all messages in the thread!"
+ );
+
+ // Now try selecting the thread from a non-root message.
+ select_click_row(1);
+ EventUtils.synthesizeKey("a", { accelKey: true, shiftKey: true });
+ Assert.ok(
+ win.gDBView.selection.count == msgsPerThread,
+ "Didn't select all messages in the thread!"
+ );
+});
+
+add_task(async function test_cross_folder_select_thread() {
+ await be_in_folder(multiVirtFolder);
+ let win = get_about_3pane();
+ make_display_threaded();
+ expand_all_threads();
+
+ // Try selecting the thread from the root message.
+ select_click_row(0);
+ EventUtils.synthesizeKey("a", { accelKey: true, shiftKey: true });
+ Assert.ok(
+ win.gDBView.selection.count == msgsPerThread,
+ "Didn't select all messages in the thread!"
+ );
+
+ // Now try selecting the thread from a non-root message.
+ select_click_row(1);
+ EventUtils.synthesizeKey("a", { accelKey: true, shiftKey: true });
+ Assert.ok(
+ win.gDBView.selection.count == msgsPerThread,
+ "Didn't select all messages in the thread!"
+ );
+});
diff --git a/comm/mail/test/browser/folder-display/browser_watchIgnoreThread.js b/comm/mail/test/browser/folder-display/browser_watchIgnoreThread.js
new file mode 100644
index 0000000000..b0036aee4d
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/browser_watchIgnoreThread.js
@@ -0,0 +1,150 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test that "watch thread" and "ignore thread" works correctly.
+ */
+
+"use strict";
+
+var {
+ add_message_sets_to_folders,
+ assert_not_shown,
+ assert_selected_and_displayed,
+ assert_visible,
+ be_in_folder,
+ create_folder,
+ create_thread,
+ expand_all_threads,
+ inboxFolder,
+ make_display_threaded,
+ mc,
+ select_click_row,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var { click_menus_in_sequence } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+var folder;
+var thread1, thread2, thread3;
+
+add_setup(async function () {
+ document.getElementById("toolbar-menubar").removeAttribute("autohide");
+ folder = await create_folder("WatchIgnoreThreadTest");
+ thread1 = create_thread(3);
+ thread2 = create_thread(4);
+ thread3 = create_thread(5);
+ await add_message_sets_to_folders([folder], [thread1, thread2, thread3]);
+
+ await be_in_folder(folder);
+ make_display_threaded();
+ expand_all_threads();
+
+ registerCleanupFunction(() => {
+ document.getElementById("toolbar-menubar").autohide = true;
+ });
+});
+
+/**
+ * Click one of the menu items in the View | Messages menu.
+ *
+ * @param {string} id - The id of the menu item to click.
+ */
+async function clickViewMessagesItem(id) {
+ EventUtils.synthesizeMouseAtCenter(
+ mc.window.document.getElementById("menu_View"),
+ {},
+ mc.window.document.getElementById("menu_View").ownerGlobal
+ );
+ await click_menus_in_sequence(
+ mc.window.document.getElementById("menu_View_Popup"),
+ [{ id: "viewMessagesMenu" }, { id }]
+ );
+}
+
+/**
+ * Test that Ignore Thread works as expected.
+ */
+add_task(async function test_ignore_thread() {
+ let t1root = thread1.getMsgHdr(0);
+
+ let t1second = select_click_row(1);
+ assert_selected_and_displayed(t1second);
+
+ // Ignore this thread.
+ EventUtils.synthesizeKey("K", { shiftKey: false, accelKey: false });
+
+ // The first msg in the next thread should now be selected.
+ let t2root = thread2.getMsgHdr(0);
+ assert_selected_and_displayed(t2root);
+
+ // The ignored thread should still be visible (with an ignored icon).
+ assert_visible(t1root);
+
+ // Go to another folder then back. Ignored messages should now be hidden.
+ await be_in_folder(inboxFolder);
+ await be_in_folder(folder);
+ select_click_row(0);
+ assert_selected_and_displayed(t2root);
+});
+
+/**
+ * Test that ignored threads are shown when the View | Threads |
+ * Ignored Threads option is checked.
+ */
+add_task(async function test_view_threads_ignored_threads() {
+ let t1root = thread1.getMsgHdr(0);
+ let t2root = thread2.getMsgHdr(0);
+
+ // Check "Ignored Threads" - the ignored messages should appear =>
+ // the first row is the first message of the first thread.
+ // await clickViewMessagesItem("viewIgnoredThreadsMenuItem");
+ goDoCommand("cmd_viewIgnoredThreads");
+ select_click_row(0);
+ assert_selected_and_displayed(t1root);
+
+ // Uncheck "Ignored Threads" - the ignored messages should get hidden.
+ // await clickViewMessagesItem("viewIgnoredThreadsMenuItem");
+ goDoCommand("cmd_viewIgnoredThreads");
+ select_click_row(0);
+ assert_selected_and_displayed(t2root);
+ assert_not_shown(thread1.msgHdrList);
+}).__skipMe = AppConstants.platform == "macosx";
+
+/**
+ * Test that Watch Thread makes the thread watched.
+ */
+add_task(async function test_watch_thread() {
+ let t2second = select_click_row(1);
+ let t3root = thread3.getMsgHdr(0);
+ assert_selected_and_displayed(t2second);
+
+ // Watch this thread.
+ EventUtils.synthesizeKey("W", { shiftKey: false, accelKey: false });
+
+ // Choose "Watched Threads with Unread".
+ // await clickViewMessagesItem("viewWatchedThreadsWithUnreadMenuItem");
+ goDoCommand("cmd_viewWatchedThreadsWithUnread");
+ select_click_row(1);
+ assert_selected_and_displayed(t2second);
+ assert_not_shown(thread1.msgHdrList);
+ assert_not_shown(thread3.msgHdrList);
+
+ // Choose "All Messages" again.
+ // await clickViewMessagesItem("viewAllMessagesMenuItem");
+ goDoCommand("cmd_viewAllMsgs");
+ assert_not_shown(thread1.msgHdrList); // still ignored (and now shown)
+ select_click_row(thread2.msgHdrList.length);
+ assert_selected_and_displayed(t3root);
+
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+}).__skipMe = AppConstants.platform == "macosx";
diff --git a/comm/mail/test/browser/folder-display/data/test-invalid-vcard.eml b/comm/mail/test/browser/folder-display/data/test-invalid-vcard.eml
new file mode 100644
index 0000000000..6ee2f4865a
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/data/test-invalid-vcard.eml
@@ -0,0 +1,25 @@
+From - Tue Jun 21 20:58:09 2020
+MIME-Version: 1.0
+Message-ID: <vcard.invalid@link.invalid>
+From: <meister@mail.example.com>
+To: Hugo <hugo@example.com>
+Subject: this contains an invalid vcard
+Date: Tue, 21 Jun 2020 20:45:48 +0200
+Content-Type: multipart/mixed;
+ boundary="------------B16B2089EF5F4ADD84A4E66F"
+
+--------------B16B2089EF5F4ADD84A4E66F
+Content-Type: text/plain; charset=UTF-8
+
+has an attached vcf which has invalid data (null)
+
+--------------B16B2089EF5F4ADD84A4E66F
+Content-Type: text/vcard;
+ name="contentisnull.vcf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="contentisnull.vcf"
+
+bnVsbA==
+--------------B16B2089EF5F4ADD84A4E66F--
+
diff --git a/comm/mail/test/browser/folder-display/head.js b/comm/mail/test/browser/folder-display/head.js
new file mode 100644
index 0000000000..9ac7d044ec
--- /dev/null
+++ b/comm/mail/test/browser/folder-display/head.js
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function getFoldersContext() {
+ return document
+ .getElementById("tabmail")
+ .currentAbout3Pane.document.getElementById("folderPaneContext");
+}
+
+function getMailContext() {
+ return document
+ .getElementById("tabmail")
+ .currentAbout3Pane.document.getElementById("mailContext");
+}
+
+/**
+ * Helper method to switch to a cards view with vertical layout.
+ */
+async function ensure_cards_view() {
+ const { threadTree, threadPane } =
+ document.getElementById("tabmail").currentAbout3Pane;
+
+ Services.prefs.setIntPref("mail.pane_config.dynamic", 2);
+ Services.xulStore.setValue(
+ "chrome://messenger/content/messenger.xhtml",
+ "threadPane",
+ "view",
+ "cards"
+ );
+ threadPane.updateThreadView("cards");
+
+ await BrowserTestUtils.waitForCondition(
+ () => threadTree.getAttribute("rows") == "thread-card",
+ "The tree view switched to a cards layout"
+ );
+}
+
+/**
+ * Helper method to switch to a table view with classic layout.
+ */
+async function ensure_table_view() {
+ const { threadTree, threadPane } =
+ document.getElementById("tabmail").currentAbout3Pane;
+
+ Services.prefs.setIntPref("mail.pane_config.dynamic", 0);
+ Services.xulStore.setValue(
+ "chrome://messenger/content/messenger.xhtml",
+ "threadPane",
+ "view",
+ "table"
+ );
+ threadPane.updateThreadView("table");
+
+ await BrowserTestUtils.waitForCondition(
+ () => threadTree.getAttribute("rows") == "thread-row",
+ "The tree view switched to a table layout"
+ );
+}
+
+registerCleanupFunction(() => {
+ let tabmail = document.getElementById("tabmail");
+ Assert.equal(
+ tabmail.tabInfo.length,
+ 1,
+ "only the first tab should remain open"
+ );
+ tabmail.closeOtherTabs(tabmail.tabInfo[0]);
+ tabmail.currentTabInfo.folderPaneVisible = true;
+ tabmail.currentTabInfo.messagePaneVisible = true;
+
+ Services.xulStore.removeDocument(
+ "chrome://messenger/content/messenger.xhtml"
+ );
+ Services.prefs.clearUserPref("mail.pane_config.dynamic");
+});