summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/tabmail/browser_closing.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/test/browser/tabmail/browser_closing.js')
-rw-r--r--comm/mail/test/browser/tabmail/browser_closing.js407
1 files changed, 407 insertions, 0 deletions
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]]);
+});