From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mail/test/browser/tabmail/browser.ini | 14 + comm/mail/test/browser/tabmail/browser_closing.js | 407 ++++++++++++++++++ .../mail/test/browser/tabmail/browser_customize.js | 145 +++++++ .../mail/test/browser/tabmail/browser_dragndrop.js | 475 +++++++++++++++++++++ .../mail/test/browser/tabmail/browser_tabSwitch.js | 344 +++++++++++++++ 5 files changed, 1385 insertions(+) create mode 100644 comm/mail/test/browser/tabmail/browser.ini create mode 100644 comm/mail/test/browser/tabmail/browser_closing.js create mode 100644 comm/mail/test/browser/tabmail/browser_customize.js create mode 100644 comm/mail/test/browser/tabmail/browser_dragndrop.js create mode 100644 comm/mail/test/browser/tabmail/browser_tabSwitch.js (limited to 'comm/mail/test/browser/tabmail') 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" + ); +}); -- cgit v1.2.3