diff options
Diffstat (limited to 'comm/mail/test/browser/folder-display')
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"); +}); |