summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/tabmail
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/test/browser/tabmail')
-rw-r--r--comm/mail/test/browser/tabmail/browser.ini14
-rw-r--r--comm/mail/test/browser/tabmail/browser_closing.js407
-rw-r--r--comm/mail/test/browser/tabmail/browser_customize.js145
-rw-r--r--comm/mail/test/browser/tabmail/browser_dragndrop.js475
-rw-r--r--comm/mail/test/browser/tabmail/browser_tabSwitch.js344
5 files changed, 1385 insertions, 0 deletions
diff --git a/comm/mail/test/browser/tabmail/browser.ini b/comm/mail/test/browser/tabmail/browser.ini
new file mode 100644
index 0000000000..55ed4e25b5
--- /dev/null
+++ b/comm/mail/test/browser/tabmail/browser.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+prefs =
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+ datareporting.policy.dataSubmissionPolicyBypassNotification=true
+subsuite = thunderbird
+
+[browser_closing.js]
+[browser_customize.js]
+[browser_dragndrop.js]
+[browser_tabSwitch.js]
diff --git a/comm/mail/test/browser/tabmail/browser_closing.js b/comm/mail/test/browser/tabmail/browser_closing.js
new file mode 100644
index 0000000000..b0caa65bc0
--- /dev/null
+++ b/comm/mail/test/browser/tabmail/browser_closing.js
@@ -0,0 +1,407 @@
+/* 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 tabmail behaviour when tabs close.
+ */
+
+"use strict";
+
+var {
+ assert_selected_tab,
+ be_in_folder,
+ collapse_all_threads,
+ create_folder,
+ make_display_threaded,
+ make_message_sets_in_folders,
+ mc,
+ open_selected_message_in_new_tab,
+ open_selected_messages,
+ select_click_row,
+ switch_tab,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var gFolder;
+
+var MSGS_PER_THREAD = 3;
+
+add_setup(async function () {
+ gFolder = await create_folder("test-tabmail-closing folder");
+ await make_message_sets_in_folders(
+ [gFolder],
+ [{ msgsPerThread: MSGS_PER_THREAD }]
+ );
+});
+
+/**
+ * Test that if we open up a message in a tab from the inbox tab, that
+ * if we immediately close that tab, we switch back to the inbox tab.
+ */
+add_task(async function test_closed_single_message_tab_returns_to_inbox() {
+ await be_in_folder(gFolder);
+ make_display_threaded();
+ let inboxTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+
+ select_click_row(0);
+ // Open a message in a new tab...
+ await open_selected_message_in_new_tab(false);
+
+ // Open a second message in a new tab...
+ await switch_tab(0);
+ select_click_row(1);
+ await open_selected_message_in_new_tab(false);
+
+ // Close the second tab
+ mc.window.document.getElementById("tabmail").closeTab(2);
+
+ // We should have gone back to the inbox tab
+ assert_selected_tab(inboxTab);
+
+ // Close the first tab
+ mc.window.document.getElementById("tabmail").closeTab(1);
+});
+
+/**
+ * Test that if we open up some message tabs from the inbox tab, and then
+ * switch around in those tabs, closing the tabs doesn't immediately jump
+ * you back to the inbox tab.
+ */
+add_task(async function test_does_not_go_to_opener_if_switched() {
+ await be_in_folder(gFolder);
+ make_display_threaded();
+
+ select_click_row(0);
+ // Open a message in a new tab...
+ await open_selected_message_in_new_tab(false);
+
+ // Open a second message in a new tab...
+ await switch_tab(0);
+ select_click_row(1);
+ await open_selected_message_in_new_tab(false);
+
+ // Switch to the first tab
+ await switch_tab(1);
+ let firstTab = mc.window.document.getElementById("tabmail").currentTabInfo;
+
+ // Switch back to the second tab
+ await switch_tab(2);
+
+ // Close the second tab
+ mc.window.document.getElementById("tabmail").closeTab(2);
+
+ // We should have gone back to the second tab
+ assert_selected_tab(firstTab);
+
+ // Close the first tab
+ mc.window.document.getElementById("tabmail").closeTab(1);
+});
+
+/**
+ * Test that if we open a whole thread up in message tabs, closing
+ * the last message tab takes us to the second last message tab as opposed
+ * to the inbox tab.
+ */
+add_task(async function test_opening_thread_in_tabs_closing_behaviour() {
+ await be_in_folder(gFolder);
+ make_display_threaded();
+ collapse_all_threads();
+
+ // Open a thread as a series of message tabs.
+ select_click_row(0);
+ open_selected_messages(mc);
+
+ // At this point, the last message tab should be selected already. We
+ // close that tab, and the second last message tab should be selected.
+ // We should close that tab, and the third last tab should be selected,
+ // etc.
+ for (let i = MSGS_PER_THREAD; i > 0; --i) {
+ let previousTab = mc.window.document
+ .getElementById("tabmail")
+ .tabContainer.getItemAtIndex(i - 1);
+ mc.window.document.getElementById("tabmail").closeTab(i);
+ Assert.equal(
+ previousTab,
+ mc.window.document.getElementById("tabmail").tabContainer.selectedItem,
+ "Expected tab at index " + (i - 1) + " to be selected."
+ );
+ }
+}).skip();
+
+/**
+ * @typedef {object} TestTab
+ * @property {Element} node - The tab's DOM node.
+ * @property {number} index - The tab's index.
+ * @property {object} info - The tabInfo for this tab, as used in #tabmail.
+ */
+
+/**
+ * Open some message tabs in the background from the folder tab.
+ *
+ * @param {number} numAdd - The number of tabs to add.
+ *
+ * @param {TestTab[]} An array of tab objects corresponding to all the open
+ * tabs.
+ */
+async function openTabs(numAdd) {
+ await be_in_folder(gFolder);
+ select_click_row(0);
+ for (let i = 0; i < numAdd; i++) {
+ await open_selected_message_in_new_tab(true);
+ }
+ let tabs = mc.window.document
+ .getElementById("tabmail")
+ .tabInfo.map((info, index) => {
+ return {
+ info,
+ index,
+ node: info.tabNode,
+ };
+ });
+ Assert.equal(tabs.length, numAdd + 1, "Have expected number of tabs");
+ return tabs;
+}
+
+/**
+ * Assert that a tab is closed.
+ *
+ * @param {TestTab} fromTab - The tab to close from.
+ * @param {Function} closeMethod - The (async) method to call on fromTab.node in
+ * order to perform the tab close.
+ * @param {TestTab} switchToTab - The tab we expect to switch to after closing
+ * tab.
+ * @param {TestTab[]} [closingTabs] - The tabs we expect to close after calling
+ * the closeMethod. This is just fromTab by default.
+ */
+async function assertClose(fromTab, closeMethod, switchToTab, closingTabs) {
+ let desc;
+ if (closingTabs) {
+ let closingIndices = closingTabs.map(t => t.index).join(",");
+ desc = `closing tab #${closingIndices} using tab #${fromTab.index}`;
+ } else {
+ closingTabs = [fromTab];
+ desc = `closing tab #${fromTab.index}`;
+ }
+ let numTabsBefore =
+ mc.window.document.getElementById("tabmail").tabInfo.length;
+ for (let tab of closingTabs) {
+ Assert.ok(
+ tab.node.parentNode,
+ `tab #${tab.index} should be in the DOM tree before ${desc}`
+ );
+ }
+ fromTab.node.scrollIntoView();
+ await closeMethod(fromTab.node);
+ for (let tab of closingTabs) {
+ Assert.ok(
+ !tab.node.parentNode,
+ `tab #${tab.index} should be removed from the DOM tree after ${desc}`
+ );
+ }
+ Assert.equal(
+ mc.window.document.getElementById("tabmail").tabInfo.length,
+ numTabsBefore - closingTabs.length,
+ `Number of tabs after ${desc}`
+ );
+ assert_selected_tab(
+ switchToTab.info,
+ `tab #${switchToTab.index} is selected after ${desc}`
+ );
+}
+
+/**
+ * Close a tab using its close button.
+ *
+ * @param {Element} tab - The tab to close.
+ */
+function closeWithButton(tab) {
+ EventUtils.synthesizeMouseAtCenter(
+ tab.querySelector(".tab-close-button"),
+ {},
+ tab.ownerGlobal
+ );
+}
+
+/**
+ * Close a tab using a middle mouse click.
+ *
+ * @param {Element} tab - The tab to close.
+ */
+function closeWithMiddleClick(tab) {
+ EventUtils.synthesizeMouseAtCenter(tab, { button: 1 }, tab.ownerGlobal);
+}
+
+/**
+ * Close the currently selected tab.
+ *
+ * @param {Element} tab - The tab to close.
+ */
+function closeWithKeyboard(tab) {
+ if (AppConstants.platform == "macosx") {
+ EventUtils.synthesizeKey("w", { accelKey: true }, tab.ownerGlobal);
+ } else {
+ EventUtils.synthesizeKey("w", { ctrlKey: true }, tab.ownerGlobal);
+ }
+}
+
+/**
+ * Open the context menu of a tab.
+ *
+ * @param {Element} tab - The tab to open the context menu of.
+ */
+async function openContextMenu(tab) {
+ let win = tab.ownerGlobal;
+ let contextMenu = win.document.getElementById("tabContextMenu");
+ let shownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ tab,
+ { type: "contextmenu", button: 2 },
+ win
+ );
+ await shownPromise;
+}
+
+/**
+ * Close the context menu, without selecting anything.
+ *
+ * @param {Element} tab - The tab to close the context menu of.
+ */
+async function closeContextMenu(tab) {
+ let win = tab.ownerGlobal;
+ let contextMenu = win.document.getElementById("tabContextMenu");
+ let hiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.hidePopup();
+ await hiddenPromise;
+}
+
+/**
+ * Open a tab's context menu and select an item.
+ *
+ * @param {Element} tab - The tab to open the context menu on.
+ * @param {string} itemId - The id of the menu item to select.
+ */
+async function selectFromContextMenu(tab, itemId) {
+ let doc = tab.ownerDocument;
+ let contextMenu = doc.getElementById("tabContextMenu");
+ let item = doc.getElementById(itemId);
+ await openContextMenu(tab);
+ let hiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+ contextMenu.activateItem(item);
+ await hiddenPromise;
+}
+
+/**
+ * Close a tab using its context menu.
+ *
+ * @param {Element} tab - The tab to close.
+ */
+async function closeWithContextMenu(tab) {
+ await selectFromContextMenu(tab, "tabContextMenuClose");
+}
+
+/**
+ * Close all other tabs using a tab's context menu.
+ *
+ * @param {Element} tab - The tab to not close.
+ */
+async function closeOtherTabsWithContextMenu(tab) {
+ await selectFromContextMenu(tab, "tabContextMenuCloseOtherTabs");
+}
+
+/**
+ * Test closing unselected tabs with the mouse or keyboard.
+ */
+add_task(async function test_close_unselected_tab_methods() {
+ let tabs = await openTabs(3);
+
+ // Can't close the first tab.
+ Assert.ok(
+ BrowserTestUtils.is_hidden(tabs[0].node.querySelector(".tab-close-button")),
+ "Close button should be hidden for the first tab"
+ );
+ // Middle click does nothing.
+ closeWithMiddleClick(tabs[0].node);
+ assert_selected_tab(tabs[0].info);
+ // Keyboard shortcut does nothing.
+ closeWithKeyboard(tabs[0].node);
+ assert_selected_tab(tabs[0].info);
+ // Context close item is disabled.
+ await openContextMenu(tabs[0].node);
+ Assert.ok(
+ mc.window.document.getElementById("tabContextMenuClose").disabled,
+ "Close context menu item should be disabled for the first tab"
+ );
+ await closeContextMenu(tabs[0].node);
+
+ // Close unselected tabs. The selected tab should stay the same.
+ await assertClose(tabs[3], closeWithButton, tabs[0]);
+ await assertClose(tabs[1], closeWithMiddleClick, tabs[0]);
+ await assertClose(tabs[2], closeWithContextMenu, tabs[0]);
+ // Keyboard shortcut cannot be used to close an unselected tab.
+});
+
+/**
+ * Test closing selected tabs with the mouse or keyboard.
+ */
+add_task(async function test_close_selected_tab_methods() {
+ let tabs = await openTabs(4);
+
+ // Select tab by clicking it.
+ EventUtils.synthesizeMouseAtCenter(tabs[4].node, {}, mc.window);
+ assert_selected_tab(tabs[4].info);
+ await assertClose(tabs[4], closeWithButton, tabs[3]);
+
+ // Select tab #2 by clicking tab #3 and using the shortcut to go back.
+ EventUtils.synthesizeMouseAtCenter(tabs[3].node, {}, mc.window);
+ assert_selected_tab(tabs[3].info);
+ EventUtils.synthesizeKey(
+ "VK_TAB",
+ { ctrlKey: true, shiftKey: true },
+ mc.window
+ );
+ assert_selected_tab(tabs[2].info);
+ await assertClose(tabs[2], closeWithKeyboard, tabs[3]);
+
+ // Note: Current open tabs is: #0, #1, #2, #3.
+
+ // Select tab #1 by using the shortcut to go forward from tab #0.
+ EventUtils.synthesizeMouseAtCenter(tabs[0].node, {}, mc.window);
+ assert_selected_tab(tabs[0].info);
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true }, mc.window);
+ assert_selected_tab(tabs[1].info);
+ await assertClose(tabs[1], closeWithMiddleClick, tabs[3]);
+
+ // Note: Current open tabs is: #0, #3.
+ // Close tabs #3 using the context menu.
+ await assertClose(tabs[3], closeWithContextMenu, tabs[0]);
+});
+
+/**
+ * Test closing other tabs with the context menu.
+ */
+add_task(async function test_close_other_tabs() {
+ let tabs = await openTabs(3);
+
+ EventUtils.synthesizeMouseAtCenter(tabs[3].node, {}, mc.window);
+ assert_selected_tab(tabs[3].info);
+ // Close tabs #1 and #2 using the context menu of #3.
+ await assertClose(tabs[3], closeOtherTabsWithContextMenu, tabs[3], [
+ tabs[1],
+ tabs[2],
+ ]);
+
+ // Note: Current open tabs is: #0 #3.
+ // The tab #3 closeOtherItem is now disabled since only tab #0 is left, which
+ // cannot be closed.
+ await openContextMenu(tabs[3].node);
+ Assert.ok(
+ mc.window.document.getElementById("tabContextMenuCloseOtherTabs").disabled,
+ "Close context menu item should be disabled for the first tab"
+ );
+ await closeContextMenu(tabs[3].node);
+
+ // But we can close tab #3 using tab #0 context menu.
+ await assertClose(tabs[0], closeOtherTabsWithContextMenu, tabs[0], [tabs[3]]);
+});
diff --git a/comm/mail/test/browser/tabmail/browser_customize.js b/comm/mail/test/browser/tabmail/browser_customize.js
new file mode 100644
index 0000000000..d9a0fc777d
--- /dev/null
+++ b/comm/mail/test/browser/tabmail/browser_customize.js
@@ -0,0 +1,145 @@
+/* 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 customization features of the tabs toolbar.
+ */
+
+"use strict";
+
+const { close_popup, mc, wait_for_popup_to_open } = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+const { drag_n_drop_element } = ChromeUtils.import(
+ "resource://testing-common/mozmill/MouseEventHelpers.jsm"
+);
+
+const { click_through_appmenu } = ChromeUtils.import(
+ "resource://testing-common/mozmill/WindowHelpers.jsm"
+);
+
+const { wait_for_element_visible, wait_for_element_invisible } =
+ ChromeUtils.import("resource://testing-common/mozmill/DOMHelpers.jsm");
+
+add_setup(function () {
+ Services.prefs.setBoolPref("mail.tabs.autoHide", false);
+});
+
+registerCleanupFunction(function () {
+ // Let's reset any and all of our changes to the toolbar
+ Services.prefs.clearUserPref("mail.tabs.autoHide");
+});
+
+/**
+ * Test that we can access the unified toolbar by clicking
+ * customize on the toolbar context menu
+ */
+add_task(async function test_open_unified_by_context() {
+ // First, ensure that the context menu is closed.
+ let contextPopup = mc.window.document.getElementById("toolbar-context-menu");
+ Assert.notEqual(
+ contextPopup.state,
+ "open",
+ "Context menu is currently open!"
+ );
+
+ // Right click on the tab bar.
+ EventUtils.synthesizeMouseAtCenter(
+ mc.window.document.getElementById("tabmail-tabs"),
+ { type: "contextmenu" },
+ window
+ );
+
+ // Ensure that the popup opened.
+ await wait_for_popup_to_open(contextPopup);
+ Assert.equal(contextPopup.state, "open", "Context menu was not opened!");
+
+ const customizeButton = document.getElementById("CustomizeMailToolbar");
+ // Click customize.
+ contextPopup.activateItem(customizeButton);
+
+ // Wait for hidden css attribute on unified toolbar
+ // customization to be removed.
+ await wait_for_element_visible(
+ window,
+ "unifiedToolbarCustomizationContainer"
+ );
+
+ // Ensure messengerWindow (HTML element) has customizingUnifiedToolbar class,
+ // which means unified toolbar customization should be open.
+ Assert.ok(
+ document
+ .getElementById("messengerWindow")
+ .classList.contains("customizingUnifiedToolbar"),
+ "customizingUnifiedToolbar class not found on messengerWindow element"
+ );
+
+ // Click cancel.
+ const cancelButton = document.getElementById(
+ "unifiedToolbarCustomizationCancel"
+ );
+ cancelButton.click();
+
+ // Wait for hidden css attribute on Unified Toolbar
+ // customization to be added.
+ await wait_for_element_invisible(
+ window,
+ "unifiedToolbarCustomizationContainer"
+ );
+
+ await close_popup(mc, contextPopup);
+});
+
+/**
+ * Test that we can access the unified toolbar customization by clicking
+ * the toolbar layout menu option
+ */
+add_task(async function test_open_unified_by_menu() {
+ // First, ensure that the menu is closed.
+ let appMenu = mc.window.document.getElementById("appMenu-popup");
+ Assert.notEqual(
+ appMenu.getAttribute("panelopen"),
+ "true",
+ "appMenu-popup is currently open!"
+ );
+
+ // Click through app menu to view unified toolbar.
+ click_through_appmenu(
+ [{ id: "appmenu_View" }, { id: "appmenu_Toolbars" }],
+ { id: "appmenu_toolbarLayout" },
+ window
+ );
+
+ // Wait for hidden css attribute on unified toolbar
+ // customization to be removed.
+ await wait_for_element_visible(
+ window,
+ "unifiedToolbarCustomizationContainer"
+ );
+
+ // Ensure messengerWindow (HTML element) has customizingUnifiedToolbar class,
+ // which means unified toolbar customization should be open.
+ Assert.ok(
+ document
+ .getElementById("messengerWindow")
+ .classList.contains("customizingUnifiedToolbar"),
+ "customizingUnifiedToolbar class not found on messengerWindow element"
+ );
+
+ // Click cancel.
+ const cancelButton = document.getElementById(
+ "unifiedToolbarCustomizationCancel"
+ );
+ cancelButton.click();
+
+ // Wait for hidden css attribute on unified toolbar
+ // customization to be added.
+ await wait_for_element_invisible(
+ window,
+ "unifiedToolbarCustomizationContainer"
+ );
+
+ await close_popup(mc, appMenu);
+});
diff --git a/comm/mail/test/browser/tabmail/browser_dragndrop.js b/comm/mail/test/browser/tabmail/browser_dragndrop.js
new file mode 100644
index 0000000000..2c09c6f621
--- /dev/null
+++ b/comm/mail/test/browser/tabmail/browser_dragndrop.js
@@ -0,0 +1,475 @@
+/* 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";
+
+// Double timeout for code coverage runs.
+if (AppConstants.MOZ_CODE_COVERAGE) {
+ requestLongerTimeout(2);
+}
+
+/*
+ * Test rearanging tabs via drag'n'drop.
+ */
+
+var EventUtils = ChromeUtils.import(
+ "resource://testing-common/mozmill/EventUtils.jsm"
+);
+
+var {
+ assert_folder_selected_and_displayed,
+ assert_number_of_tabs_open,
+ assert_selected_and_displayed,
+ be_in_folder,
+ close_popup,
+ create_folder,
+ display_message_in_folder_tab,
+ get_about_3pane,
+ make_message_sets_in_folders,
+ mc,
+ open_folder_in_new_window,
+ open_selected_message_in_new_tab,
+ select_click_row,
+ switch_tab,
+ wait_for_message_display_completion,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+var {
+ drag_n_drop_element,
+ synthesize_drag_end,
+ synthesize_drag_start,
+ synthesize_drag_over,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/MouseEventHelpers.jsm"
+);
+var { async_plan_for_new_window, close_window, wait_for_new_window } =
+ ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+
+var folder;
+var msgHdrsInFolder = [];
+
+// The number of messages in folder.
+var NUM_MESSAGES_IN_FOLDER = 15;
+
+add_setup(async function () {
+ folder = await create_folder("MessageFolder");
+ await make_message_sets_in_folders(
+ [folder],
+ [{ count: NUM_MESSAGES_IN_FOLDER }]
+ );
+ msgHdrsInFolder = [...folder.messages];
+ folder.markAllMessagesRead(null);
+
+ await be_in_folder(folder);
+});
+
+registerCleanupFunction(async function () {
+ folder.deleteSelf(null);
+});
+
+/**
+ * Tests reordering tabs by drag'n'drop within the tabbar
+ *
+ * It opens additional movable and closable tabs. The picks the first
+ * movable tab and drops it onto the third movable tab.
+ */
+add_task(async function test_tab_reorder_tabbar() {
+ let tabmail = mc.window.document.getElementById("tabmail");
+ // Ensure only one tab is open, otherwise our test most likey fail anyway.
+ tabmail.closeOtherTabs(0);
+ assert_number_of_tabs_open(1);
+
+ await be_in_folder(folder);
+
+ // Open four tabs
+ for (let idx = 0; idx < 4; idx++) {
+ select_click_row(idx);
+ await open_selected_message_in_new_tab(true);
+ }
+
+ // Check if every thing is correctly initialized
+ assert_number_of_tabs_open(5);
+
+ Assert.ok(
+ tabmail.tabModes.mailMessageTab.tabs[0] == tabmail.tabInfo[1],
+ " tabMode.tabs and tabInfo out of sync"
+ );
+
+ Assert.ok(
+ tabmail.tabModes.mailMessageTab.tabs[1] ==
+ mc.window.document.getElementById("tabmail").tabInfo[2],
+ " tabMode.tabs and tabInfo out of sync"
+ );
+
+ Assert.ok(
+ tabmail.tabModes.mailMessageTab.tabs[2] == tabmail.tabInfo[3],
+ " tabMode.tabs and tabInfo out of sync"
+ );
+
+ // Start dragging the first tab
+ await switch_tab(1);
+ assert_selected_and_displayed(msgHdrsInFolder[0]);
+
+ let tab1 = tabmail.tabContainer.allTabs[1];
+ let tab3 = tabmail.tabContainer.allTabs[3];
+
+ drag_n_drop_element(
+ tab1,
+ mc.window,
+ tab3,
+ mc.window,
+ 0.75,
+ 0.0,
+ tabmail.tabContainer
+ );
+
+ wait_for_message_display_completion(mc);
+
+ // if every thing went well...
+ assert_number_of_tabs_open(5);
+
+ // ... we should find tab1 at the third position...
+ Assert.equal(tab1, tabmail.tabContainer.allTabs[3], "Moving tab1 failed");
+ await switch_tab(3);
+ assert_selected_and_displayed(msgHdrsInFolder[0]);
+
+ // ... while tab3 moves one up and gets second.
+ Assert.ok(tab3 == tabmail.tabContainer.allTabs[2], "Moving tab3 failed");
+ await switch_tab(2);
+ assert_selected_and_displayed(msgHdrsInFolder[2]);
+
+ // we have one "message" tab and three "folder" tabs, thus tabInfo[1-3] and
+ // tabMode["message"].tabs[0-2] have to be same, otherwise something went
+ // wrong while moving tabs around
+ Assert.ok(
+ tabmail.tabModes.mailMessageTab.tabs[0] == tabmail.tabInfo[1],
+ " tabMode.tabs and tabInfo out of sync"
+ );
+
+ Assert.ok(
+ tabmail.tabModes.mailMessageTab.tabs[1] == tabmail.tabInfo[2],
+ " tabMode.tabs and tabInfo out of sync"
+ );
+
+ Assert.ok(
+ tabmail.tabModes.mailMessageTab.tabs[2] == tabmail.tabInfo[3],
+ " tabMode.tabs and tabInfo out of sync"
+ );
+ teardownTest();
+});
+
+/**
+ * Tests drag'n'drop tab reordering between windows
+ */
+add_task(async function test_tab_reorder_window() {
+ let tabmail = mc.window.document.getElementById("tabmail");
+ // Ensure only one tab is open, otherwise our test most likey fail anyway.
+ tabmail.closeOtherTabs(0);
+ assert_number_of_tabs_open(1);
+
+ let mc2 = null;
+
+ await be_in_folder(folder);
+
+ // Open a new tab...
+ select_click_row(1);
+ await open_selected_message_in_new_tab(false);
+
+ assert_number_of_tabs_open(2);
+
+ await switch_tab(1);
+ assert_selected_and_displayed(msgHdrsInFolder[1]);
+
+ // ...and then a new 3 pane as our drop target.
+ mc2 = open_folder_in_new_window(folder);
+
+ // Start dragging the first tab ...
+ let tabA = tabmail.tabContainer.allTabs[1];
+ Assert.ok(tabA, "No movable Tab");
+
+ // We drop onto the Folder Tab, it is guaranteed to exist.
+ let tabmail2 = mc2.window.document.getElementById("tabmail");
+ let tabB = tabmail2.tabContainer.allTabs[0];
+ Assert.ok(tabB, "No movable Tab");
+
+ drag_n_drop_element(
+ tabA,
+ mc.window,
+ tabB,
+ mc2.window,
+ 0.75,
+ 0.0,
+ tabmail.tabContainer
+ );
+
+ wait_for_message_display_completion(mc2);
+
+ Assert.ok(
+ tabmail.tabContainer.allTabs.length == 1,
+ "Moving tab to new window failed, tab still in old window"
+ );
+
+ Assert.ok(
+ tabmail2.tabContainer.allTabs.length == 2,
+ "Moving tab to new window failed, no new tab in new window"
+ );
+
+ assert_selected_and_displayed(mc2, msgHdrsInFolder[1]);
+ teardownTest();
+});
+
+/**
+ * Tests detaching tabs into windows via drag'n'drop
+ */
+add_task(async function test_tab_reorder_detach() {
+ let tabmail = mc.window.document.getElementById("tabmail");
+ // Ensure only one tab is open, otherwise our test most likey fail anyway.
+ tabmail.closeOtherTabs(0);
+ assert_number_of_tabs_open(1);
+
+ let mc2 = null;
+
+ await be_in_folder(folder);
+
+ // Open a new tab...
+ select_click_row(2);
+ await open_selected_message_in_new_tab(false);
+
+ assert_number_of_tabs_open(2);
+
+ // ... if every thing works we should expect a new window...
+ let newWindowPromise = async_plan_for_new_window("mail:3pane");
+
+ // ... now start dragging
+ tabmail.switchToTab(1);
+
+ let tab1 = tabmail.tabContainer.allTabs[1];
+ let dropContent = mc.window.document.getElementById("tabpanelcontainer");
+
+ let dt = synthesize_drag_start(mc.window, tab1, tabmail.tabContainer);
+
+ synthesize_drag_over(mc.window, dropContent, dt);
+
+ // notify tab1 drag has ended
+ let dropRect = dropContent.getBoundingClientRect();
+ synthesize_drag_end(mc.window, dropContent, tab1, dt, {
+ screenX: dropContent.screenX + dropRect.width / 2,
+ screenY: dropContent.screenY + dropRect.height / 2,
+ });
+
+ // ... and wait for the new window
+ mc2 = await newWindowPromise;
+ let tabmail2 = mc2.window.document.getElementById("tabmail");
+ await TestUtils.waitForCondition(
+ () => tabmail2.tabInfo[1]?.chromeBrowser,
+ "waiting for a second tab to open in the new window"
+ );
+ wait_for_message_display_completion(mc2, true);
+
+ Assert.ok(
+ tabmail.tabContainer.allTabs.length == 1,
+ "Moving tab to new window failed, tab still in old window"
+ );
+
+ Assert.ok(
+ tabmail2.tabContainer.allTabs.length == 2,
+ "Moving tab to new window failed, no new tab in new window"
+ );
+
+ assert_selected_and_displayed(mc2, msgHdrsInFolder[2]);
+ teardownTest();
+});
+
+/**
+ * Test undo of recently closed tabs.
+ */
+add_task(async function test_tab_undo() {
+ let tabmail = mc.window.document.getElementById("tabmail");
+ // Ensure only one tab is open, otherwise our test most likey fail anyway.
+ tabmail.closeOtherTabs(0);
+ assert_number_of_tabs_open(1);
+
+ await be_in_folder(folder);
+
+ // Open five tabs...
+ for (let idx = 0; idx < 5; idx++) {
+ select_click_row(idx);
+ await open_selected_message_in_new_tab(true);
+ }
+
+ assert_number_of_tabs_open(6);
+
+ await switch_tab(2);
+ assert_selected_and_displayed(msgHdrsInFolder[1]);
+
+ tabmail.closeTab(2);
+ // This tab should not be added to recently closed tabs...
+ // ... thus it can't be restored
+ tabmail.closeTab(2, true);
+ tabmail.closeTab(2);
+
+ assert_number_of_tabs_open(3);
+ assert_selected_and_displayed(mc, msgHdrsInFolder[4]);
+
+ tabmail.undoCloseTab();
+ wait_for_message_display_completion();
+ assert_number_of_tabs_open(4);
+ assert_selected_and_displayed(mc, msgHdrsInFolder[3]);
+
+ // msgHdrsInFolder[2] won't be restored, it was closed with disabled undo.
+
+ tabmail.undoCloseTab();
+ wait_for_message_display_completion();
+ assert_number_of_tabs_open(5);
+ assert_selected_and_displayed(mc, msgHdrsInFolder[1]);
+ teardownTest();
+});
+
+async function _synthesizeRecentlyClosedMenu() {
+ let tab =
+ mc.window.document.getElementById("tabmail").tabContainer.allTabs[1];
+ EventUtils.synthesizeMouseAtCenter(
+ tab,
+ { type: "contextmenu", button: 2 },
+ tab.ownerGlobal
+ );
+
+ let tabContextMenu = mc.window.document.getElementById("tabContextMenu");
+ await wait_for_popup_to_open(tabContextMenu);
+
+ let recentlyClosedTabs = mc.window.document.getElementById(
+ "tabContextMenuRecentlyClosed"
+ );
+
+ recentlyClosedTabs.openMenu(true);
+ await wait_for_popup_to_open(recentlyClosedTabs.menupopup);
+
+ return recentlyClosedTabs;
+}
+
+async function _teardownRecentlyClosedMenu() {
+ let menu = mc.window.document.getElementById("tabContextMenu");
+ await close_popup(mc, menu);
+}
+
+/**
+ * Tests the recently closed tabs menu.
+ */
+add_task(async function test_tab_recentlyClosed() {
+ let tabmail = mc.window.document.getElementById("tabmail");
+ // Ensure only one tab is open, otherwise our test most likey fail anyway.
+ tabmail.closeOtherTabs(0, true);
+ assert_number_of_tabs_open(1);
+
+ // We start with a clean tab history.
+ tabmail.clearRecentlyClosedTabs();
+ Assert.equal(tabmail.recentlyClosedTabs.length, 0);
+
+ // The history is cleaned so let's open 15 tabs...
+ await be_in_folder(folder);
+
+ for (let idx = 0; idx < 15; idx++) {
+ select_click_row(idx);
+ await open_selected_message_in_new_tab(true);
+ }
+
+ assert_number_of_tabs_open(16);
+
+ await switch_tab(2);
+ assert_selected_and_displayed(msgHdrsInFolder[1]);
+
+ // ... and store the tab titles, to ensure they match with the menu items.
+ let tabTitles = [];
+ for (let idx = 0; idx < 16; idx++) {
+ tabTitles.unshift(tabmail.tabInfo[idx].title);
+ }
+
+ // Start the test by closing all tabs except the first two tabs...
+ for (let idx = 0; idx < 14; idx++) {
+ tabmail.closeTab(2);
+ }
+
+ assert_number_of_tabs_open(2);
+
+ // ...then open the context menu.
+ let menu = await _synthesizeRecentlyClosedMenu();
+
+ // Check if the context menu was populated correctly...
+ Assert.ok(menu.itemCount == 12, "Failed to populate context menu");
+ for (let idx = 0; idx < 10; idx++) {
+ Assert.ok(
+ tabTitles[idx] == menu.getItemAtIndex(idx).label,
+ "Tab Title does not match Menu item"
+ );
+ }
+
+ // Restore the most recently closed tab
+ menu.menupopup.activateItem(menu.getItemAtIndex(0));
+ await _teardownRecentlyClosedMenu();
+ await new Promise(resolve => setTimeout(resolve));
+
+ wait_for_message_display_completion(mc);
+ assert_number_of_tabs_open(3);
+ assert_selected_and_displayed(msgHdrsInFolder[14]);
+
+ // The context menu should now contain one item less.
+ await _synthesizeRecentlyClosedMenu();
+
+ Assert.ok(menu.itemCount == 11, "Failed to populate context menu");
+ for (let idx = 0; idx < 9; idx++) {
+ Assert.ok(
+ tabTitles[idx + 1] == menu.getItemAtIndex(idx).label,
+ "Tab Title does not match Menu item"
+ );
+ }
+
+ // Now we restore an "random" tab.
+ menu.menupopup.activateItem(menu.getItemAtIndex(5));
+ await _teardownRecentlyClosedMenu();
+ await new Promise(resolve => setTimeout(resolve));
+
+ wait_for_message_display_completion(mc);
+ assert_number_of_tabs_open(4);
+ assert_selected_and_displayed(msgHdrsInFolder[8]);
+
+ // finally restore all tabs
+ await _synthesizeRecentlyClosedMenu();
+
+ Assert.ok(menu.itemCount == 10, "Failed to populate context menu");
+ Assert.ok(
+ tabTitles[1] == menu.getItemAtIndex(0).label,
+ "Tab Title does not match Menu item"
+ );
+ Assert.ok(
+ tabTitles[7] == menu.getItemAtIndex(5).label,
+ "Tab Title does not match Menu item"
+ );
+
+ menu.menupopup.activateItem(menu.getItemAtIndex(menu.itemCount - 1));
+ await _teardownRecentlyClosedMenu();
+ await new Promise(resolve => setTimeout(resolve));
+
+ wait_for_message_display_completion(mc);
+
+ // out of the 16 tab, we closed all except two. As the history can store
+ // only 10 items we have to endup with exactly 10 + 2 tabs.
+ assert_number_of_tabs_open(12);
+ teardownTest();
+});
+
+function teardownTest(test) {
+ // Some test cases open new windows, thus we need to ensure all
+ // opened windows get closed.
+ for (let win of Services.wm.getEnumerator("mail:3pane")) {
+ if (win != mc.window) {
+ win.close();
+ }
+ }
+
+ // clean up the tabbbar
+ mc.window.document.getElementById("tabmail").closeOtherTabs(0);
+ assert_number_of_tabs_open(1);
+}
diff --git a/comm/mail/test/browser/tabmail/browser_tabSwitch.js b/comm/mail/test/browser/tabmail/browser_tabSwitch.js
new file mode 100644
index 0000000000..38a158a66d
--- /dev/null
+++ b/comm/mail/test/browser/tabmail/browser_tabSwitch.js
@@ -0,0 +1,344 @@
+/* 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/. */
+
+add_task(async function () {
+ // Helper functions.
+
+ function assertSelected(element, name) {
+ Assert.ok(
+ element.hasAttribute("selected"),
+ `${name} has selected attribute`
+ );
+ }
+ function assertNotSelected(element, name) {
+ Assert.ok(
+ !element.hasAttribute("selected"),
+ `${name} does NOT have selected attribute`
+ );
+ }
+ function getTabElements() {
+ return [...tabmail.tabContainer.querySelectorAll("tab")];
+ }
+ function checkTabElements(expectedCount, expectedSelection) {
+ let tabElements = getTabElements();
+ Assert.equal(
+ tabElements.length,
+ expectedCount,
+ `${expectedCount} tab elements exist`
+ );
+
+ for (let i = 0; i < expectedCount; i++) {
+ if (i == expectedSelection) {
+ assertSelected(tabElements[i], `tab element ${i}`);
+ } else {
+ assertNotSelected(tabElements[i], `tab element ${i}`);
+ }
+ }
+ }
+ async function switchTab(index) {
+ let tabElement = getTabElements()[index];
+ eventPromise = BrowserTestUtils.waitForEvent(
+ tabmail.tabContainer,
+ "TabSelect"
+ );
+ EventUtils.synthesizeMouseAtCenter(tabElement, {});
+ event = await eventPromise;
+ Assert.equal(
+ event.target,
+ tabElement,
+ `TabSelect event fired from tab ${index}`
+ );
+ }
+ async function closeTab(index) {
+ let tabElement = getTabElements()[index];
+ eventPromise = BrowserTestUtils.waitForEvent(
+ tabmail.tabContainer,
+ "TabClose"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ tabElement.querySelector(".tab-close-button"),
+ {}
+ );
+ event = await eventPromise;
+ Assert.equal(
+ event.target,
+ tabElement,
+ `TabClose event fired from tab ${index}`
+ );
+ }
+
+ // Collect some elements.
+
+ let tabmail = document.getElementById("tabmail");
+ let calendarTabButton = document.getElementById("calendarButton");
+
+ let mailTabPanel = document.getElementById("mail3PaneTab1");
+ let mailTabBrowser = document.getElementById("mail3PaneTabBrowser1");
+ let folderTree = mailTabBrowser.contentDocument.getElementById("folderTree");
+ let calendarTabPanel = document.getElementById("calendarTabPanel");
+ let contentTab;
+ let contentTabPanel;
+
+ let calendarList = document.getElementById("calendar-list");
+
+ let eventPromise;
+ let event;
+
+ // Check we're in a good state to start with.
+
+ Assert.equal(tabmail.tabInfo.length, 1, "only one tab is open");
+ checkTabElements(1, 0);
+ assertSelected(mailTabPanel, "mail tab's panel");
+
+ // Set the focus on the mail tab.
+
+ folderTree.focus();
+ Assert.equal(
+ document.activeElement,
+ mailTabBrowser,
+ "mail tab's browser has focus"
+ );
+ Assert.equal(
+ mailTabBrowser.contentDocument.activeElement,
+ folderTree,
+ "folder tree has focus"
+ );
+
+ // Switch to the calendar tab.
+
+ eventPromise = BrowserTestUtils.waitForEvent(tabmail.tabContainer, "TabOpen");
+ EventUtils.synthesizeMouseAtCenter(calendarTabButton, {});
+ event = await eventPromise;
+ Assert.equal(
+ event.target,
+ getTabElements()[1],
+ "TabOpen event fired from tab 1"
+ );
+
+ checkTabElements(2, 1);
+ assertNotSelected(mailTabPanel, "mail tab's panel");
+ assertSelected(calendarTabPanel, "calendar tab's panel");
+ Assert.equal(
+ document.activeElement,
+ document.body,
+ "mail tab's browser does NOT have focus"
+ );
+ Assert.equal(
+ tabmail.tabInfo[0].lastActiveElement,
+ folderTree,
+ "mail tab's last active element should be stored"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[1].lastActiveElement,
+ "calendar tab's last active element should not be stored yet"
+ );
+
+ // Set the focus on the calendar list.
+
+ EventUtils.synthesizeMouseAtCenter(calendarList, {});
+ Assert.equal(document.activeElement, calendarList, "calendar list has focus");
+
+ // Switch to the mail tab.
+
+ await switchTab(0);
+
+ checkTabElements(2, 0);
+ assertSelected(mailTabPanel, "mail tab's panel");
+ assertNotSelected(calendarTabPanel, "calendar tab's panel");
+ Assert.equal(
+ document.activeElement,
+ mailTabBrowser,
+ "mail tab's browser has focus"
+ );
+ Assert.equal(
+ mailTabBrowser.contentDocument.activeElement,
+ folderTree,
+ "folder tree has focus"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[0].lastActiveElement,
+ "mail tab's last active element should have been cleaned up"
+ );
+ Assert.equal(
+ tabmail.tabInfo[1].lastActiveElement,
+ calendarList,
+ "calendar tab's last active element should be stored"
+ );
+
+ // Switch to the calendar tab.
+
+ await switchTab(1);
+
+ checkTabElements(2, 1);
+ assertNotSelected(mailTabPanel, "mail tab's panel");
+ assertSelected(calendarTabPanel, "calendar tab's panel");
+ Assert.equal(document.activeElement, calendarList, "calendar list has focus");
+ Assert.equal(
+ tabmail.tabInfo[0].lastActiveElement,
+ folderTree,
+ "mail tab's last active element should be stored"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[1].lastActiveElement,
+ "calendar tab's last active element should have been cleaned up"
+ );
+
+ // Open a content tab.
+
+ eventPromise = BrowserTestUtils.waitForEvent(tabmail.tabContainer, "TabOpen");
+ contentTab = window.openContentTab("https://example.org/");
+ contentTabPanel = contentTab.browser.closest(
+ ".contentTabInstance"
+ ).parentNode;
+ event = await eventPromise;
+ Assert.equal(
+ event.target,
+ getTabElements()[2],
+ "TabOpen event fired from tab 2"
+ );
+
+ checkTabElements(3, 2);
+ assertNotSelected(mailTabPanel, "mail tab's panel");
+ assertNotSelected(calendarTabPanel, "calendar tab's panel");
+ assertSelected(contentTabPanel, "content tab's panel");
+ Assert.equal(
+ document.activeElement,
+ document.body,
+ "folder tree and calendar list do NOT have focus"
+ );
+ Assert.equal(
+ tabmail.tabInfo[0].lastActiveElement,
+ folderTree,
+ "mail tab's last active element should be stored"
+ );
+ Assert.equal(
+ tabmail.tabInfo[1].lastActiveElement,
+ calendarList,
+ "calendar tab's last active element should be stored"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[2].lastActiveElement,
+ "content tab should have no last active element"
+ );
+
+ // Switch to the mail tab.
+
+ await switchTab(0);
+
+ checkTabElements(3, 0);
+ assertSelected(mailTabPanel, "mail tab's panel");
+ assertNotSelected(calendarTabPanel, "calendar tab's panel");
+ Assert.equal(
+ document.activeElement,
+ mailTabBrowser,
+ "mail tab's browser has focus"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[0].lastActiveElement,
+ "mail tab's last active element should be cleaned up"
+ );
+ Assert.equal(
+ tabmail.tabInfo[1].lastActiveElement,
+ calendarList,
+ "calendar tab's last active element should be stored"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[2].lastActiveElement,
+ "content tab should have no last active element"
+ );
+
+ // Switch to the calendar tab.
+
+ await switchTab(1);
+
+ checkTabElements(3, 1);
+ assertNotSelected(mailTabPanel, "mail tab's panel");
+ assertSelected(calendarTabPanel, "calendar tab's panel");
+ Assert.equal(document.activeElement, calendarList, "calendar list has focus");
+ Assert.equal(
+ tabmail.tabInfo[0].lastActiveElement,
+ folderTree,
+ "mail tab's last active element should be stored"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[1].lastActiveElement,
+ "calendar tab's last active element should be cleaned up"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[2].lastActiveElement,
+ "content tab should have no last active element"
+ );
+
+ // Switch to the content tab.
+
+ await switchTab(2);
+
+ checkTabElements(3, 2);
+ assertNotSelected(mailTabPanel, "mail tab's panel");
+ assertNotSelected(calendarTabPanel, "calendar tab's panel");
+ assertSelected(contentTabPanel, "content tab's panel");
+ Assert.equal(
+ document.activeElement,
+ document.body,
+ "folder tree and calendar list do NOT have focus"
+ );
+ Assert.equal(
+ tabmail.tabInfo[0].lastActiveElement,
+ folderTree,
+ "mail tab's last active element should be stored"
+ );
+ Assert.equal(
+ tabmail.tabInfo[1].lastActiveElement,
+ calendarList,
+ "calendar tab's last active element should be stored"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[2].lastActiveElement,
+ "content tab should have no last active element"
+ );
+
+ // Close the content tab.
+
+ await closeTab(2);
+
+ checkTabElements(2, 1);
+ assertNotSelected(mailTabPanel, "mail tab's panel");
+ assertSelected(calendarTabPanel, "calendar tab's panel");
+ // At this point contentTabPanel is still part of the DOM, it is removed
+ // after the TabClose event.
+ assertNotSelected(contentTabPanel, "content tab's panel");
+ Assert.equal(document.activeElement, calendarList, "calendar list has focus");
+ Assert.equal(
+ tabmail.tabInfo[0].lastActiveElement,
+ folderTree,
+ "mail tab's last active element should be stored"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[1].lastActiveElement,
+ "calendar tab's last active element should have been cleaned up"
+ );
+
+ await new Promise(resolve => setTimeout(resolve));
+ Assert.ok(
+ !contentTabPanel.parentNode,
+ "content tab's panel is removed from the DOM"
+ );
+
+ // Close the calendar tab.
+
+ await closeTab(1);
+
+ checkTabElements(1, 0);
+ assertSelected(mailTabPanel, "mail tab's panel");
+ assertNotSelected(calendarTabPanel, "calendar tab's panel");
+ Assert.equal(
+ document.activeElement,
+ mailTabBrowser,
+ "mail tab's browser has focus"
+ );
+ Assert.ok(
+ !tabmail.tabInfo[0].lastActiveElement,
+ "mail tab's last active element should have been cleaned up"
+ );
+});