summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/test/browser/browser_spacesToolbar.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/base/test/browser/browser_spacesToolbar.js')
-rw-r--r--comm/mail/base/test/browser/browser_spacesToolbar.js1173
1 files changed, 1173 insertions, 0 deletions
diff --git a/comm/mail/base/test/browser/browser_spacesToolbar.js b/comm/mail/base/test/browser/browser_spacesToolbar.js
new file mode 100644
index 0000000000..c2432a2131
--- /dev/null
+++ b/comm/mail/base/test/browser/browser_spacesToolbar.js
@@ -0,0 +1,1173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test the spaces toolbar features.
+ */
+
+/* globals gSpacesToolbar */
+
+var folderA;
+var folderB;
+var testAccount;
+
+add_setup(function () {
+ // Set up two folders.
+ window.MailServices.accounts.createLocalMailAccount();
+ testAccount = window.MailServices.accounts.accounts[0];
+ let rootFolder = testAccount.incomingServer.rootFolder;
+ rootFolder.createSubfolder("spacesToolbarA", null);
+ folderA = rootFolder.findSubFolder("spacesToolbarA");
+ rootFolder.createSubfolder("spacesToolbarB", null);
+ folderB = rootFolder.findSubFolder("spacesToolbarB");
+});
+
+registerCleanupFunction(async () => {
+ window.MailServices.accounts.removeAccount(testAccount, true);
+ // Close all opened tabs.
+ let tabmail = document.getElementById("tabmail");
+ tabmail.closeOtherTabs(tabmail.tabInfo[0]);
+ // Reset the spaces toolbar to its default visible state.
+ window.gSpacesToolbar.toggleToolbar(false);
+ // Reset the menubar visibility.
+ let menubar = document.getElementById("toolbar-menubar");
+ menubar.removeAttribute("autohide");
+ menubar.removeAttribute("inactive");
+ await new Promise(resolve => requestAnimationFrame(resolve));
+});
+
+async function assertMailShown(win = window) {
+ await TestUtils.waitForCondition(
+ () =>
+ win.document.getElementById("tabmail").currentTabInfo.mode.name ==
+ "mail3PaneTab",
+ "The mail tab should be visible"
+ );
+}
+
+async function assertAddressBookShown(win = window) {
+ await TestUtils.waitForCondition(() => {
+ let panel = win.document.querySelector(
+ // addressBookTabWrapper0, addressBookTabWrapper1, etc
+ "#tabpanelcontainer > [id^=addressBookTabWrapper][selected]"
+ );
+ if (!panel) {
+ return false;
+ }
+ let browser = panel.querySelector("[id^=addressBookTabBrowser]");
+ return browser.contentDocument.readyState == "complete";
+ }, "The address book tab should be visible and loaded");
+}
+
+async function assertChatShown(win = window) {
+ await TestUtils.waitForCondition(
+ () => win.document.getElementById("chatTabPanel").hasAttribute("selected"),
+ "The chat tab should be visible"
+ );
+}
+
+async function assertCalendarShown(win = window) {
+ await TestUtils.waitForCondition(() => {
+ return (
+ win.document
+ .getElementById("calendarTabPanel")
+ .hasAttribute("selected") &&
+ !win.document.getElementById("calendar-view-box").collapsed
+ );
+ }, "The calendar view should be visible");
+}
+
+async function assertTasksShown(win = window) {
+ await TestUtils.waitForCondition(() => {
+ return (
+ win.document
+ .getElementById("calendarTabPanel")
+ .hasAttribute("selected") &&
+ !win.document.getElementById("calendar-task-box").collapsed
+ );
+ }, "The task view should be visible");
+}
+
+async function assertSettingsShown(win = window) {
+ await TestUtils.waitForCondition(() => {
+ let panel = win.document.querySelector(
+ // preferencesTabWrapper0, preferencesTabWrapper1, etc
+ "#tabpanelcontainer > [id^=preferencesTabWrapper][selected]"
+ );
+ if (!panel) {
+ return false;
+ }
+ let browser = panel.querySelector("[id^=preferencesTabBrowser]");
+ return browser.contentDocument.readyState == "complete";
+ }, "The settings tab should be visible and loaded");
+}
+
+async function assertContentShown(url, win = window) {
+ await TestUtils.waitForCondition(() => {
+ let panel = win.document.querySelector(
+ // contentTabWrapper0, contentTabWrapper1, etc
+ "#tabpanelcontainer > [id^=contentTabWrapper][selected]"
+ );
+ if (!panel) {
+ return false;
+ }
+ let doc = panel.querySelector("[id^=contentTabBrowser]").contentDocument;
+ return doc.URL == url && doc.readyState == "complete";
+ }, `The selected content tab should show ${url}`);
+}
+
+async function sub_test_cycle_through_primary_tabs() {
+ // We can't really cycle through all buttons and tabs with a simple for loop
+ // since some tabs are actual collapsing views and other tabs are separate
+ // pages. We can improve this once the new 3pane tab is actually a standalone
+ // tab.
+
+ // Switch to address book.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("addressBookButton"),
+ {},
+ window
+ );
+ await assertAddressBookShown();
+
+ // Switch to calendar.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("calendarButton"),
+ {},
+ window
+ );
+ await assertCalendarShown();
+
+ // Switch to Mail.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("mailButton"),
+ {},
+ window
+ );
+ await assertMailShown();
+
+ // Switch to Tasks.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("tasksButton"),
+ {},
+ window
+ );
+ await assertTasksShown();
+
+ // Switch to chat.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("chatButton"),
+ {},
+ window
+ );
+ await assertChatShown();
+
+ // Switch to Settings.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("settingsButton"),
+ {},
+ window
+ );
+ await assertSettingsShown();
+
+ // Switch to Mail.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("mailButton"),
+ {},
+ window
+ );
+ await assertMailShown();
+
+ window.tabmail.closeOtherTabs(window.tabmail.tabInfo[0]);
+}
+
+add_task(async function testSpacesToolbarVisibility() {
+ let spacesToolbar = document.getElementById("spacesToolbar");
+ let toggleButton = document.getElementById("spacesToolbarReveal");
+ let pinnedButton = document.getElementById("spacesPinnedButton");
+ Assert.ok(spacesToolbar, "The spaces toolbar exists");
+
+ let assertVisibility = async function (isHidden, msg) {
+ await TestUtils.waitForCondition(
+ () => spacesToolbar.hidden == !isHidden,
+ `The spaces toolbar should be ${!isHidden ? "visible" : "hidden"}: ${msg}`
+ );
+
+ await TestUtils.waitForCondition(
+ () => toggleButton.hidden == isHidden,
+ `The toggle button should be ${isHidden ? "hidden" : "visible"}: ${msg}`
+ );
+
+ await TestUtils.waitForCondition(
+ () => pinnedButton.hidden == isHidden,
+ `The pinned button should be ${isHidden ? "hidden" : "visible"}: ${msg}`
+ );
+ };
+
+ async function toggleVisibilityWithAppMenu(expectChecked) {
+ let appMenu = document.getElementById("appMenu-popup");
+ let menuShownPromise = BrowserTestUtils.waitForEvent(appMenu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("button-appmenu"),
+ {},
+ window
+ );
+ await menuShownPromise;
+
+ let viewShownPromise = BrowserTestUtils.waitForEvent(
+ appMenu.querySelector("#appMenu-viewView"),
+ "ViewShown"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ appMenu.querySelector("#appmenu_View"),
+ {},
+ window
+ );
+ await viewShownPromise;
+
+ let toolbarShownPromise = BrowserTestUtils.waitForEvent(
+ appMenu.querySelector("#appMenu-toolbarsView"),
+ "ViewShown"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ appMenu.querySelector("#appmenu_Toolbars"),
+ {},
+ window
+ );
+ await toolbarShownPromise;
+
+ let appMenuButton = document.getElementById("appmenu_spacesToolbar");
+ Assert.equal(
+ appMenuButton.checked,
+ expectChecked,
+ `The app menu item should ${expectChecked ? "not " : ""}be checked`
+ );
+
+ EventUtils.synthesizeMouseAtCenter(appMenuButton, {}, window);
+
+ // Close the appmenu.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("button-appmenu"),
+ {},
+ window
+ );
+ }
+ await assertVisibility(true, "on initial load");
+
+ // Collapse with a mouse click.
+ let activeElement = document.activeElement;
+ let collapseButton = document.getElementById("collapseButton");
+ EventUtils.synthesizeMouseAtCenter(collapseButton, {}, window);
+ await assertVisibility(false, "after clicking collapse button");
+
+ await toggleVisibilityWithAppMenu(false);
+ await assertVisibility(true, "after revealing with the app menu");
+
+ // We already clicked the collapse button, so it should already be the
+ // focusButton for the gSpacesToolbar, and thus focusable.
+ collapseButton.focus();
+ Assert.ok(
+ collapseButton.matches(":focus"),
+ "Collapse button should be focusable"
+ );
+
+ // Hide the spaces toolbar using the collapse button, which already has focus.
+ EventUtils.synthesizeKey(" ", {}, window);
+ await assertVisibility(false, "after closing with space key press");
+ Assert.ok(
+ pinnedButton.matches(":focus"),
+ "Pinned button should be focused after closing with a key press"
+ );
+
+ // Show using the pinned button menu.
+ let pinnedMenu = document.getElementById("spacesButtonMenuPopup");
+ let pinnedMenuShown = BrowserTestUtils.waitForEvent(pinnedMenu, "popupshown");
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ await pinnedMenuShown;
+ pinnedMenu.activateItem(document.getElementById("spacesPopupButtonReveal"));
+
+ await assertVisibility(true, "after opening with pinned menu");
+ Assert.ok(
+ collapseButton.matches(":focus"),
+ "Collapse button should be focused again after showing with the pinned menu"
+ );
+
+ // Move focus to the mail button.
+ let mailButton = document.getElementById("mailButton");
+ // Loop around from the collapse button to the mailButton.
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+
+ Assert.ok(
+ mailButton.matches(":focus"),
+ "Mail button should become focused after pressing key down"
+ );
+ Assert.ok(
+ spacesToolbar.matches(":focus-within"),
+ "Spaces toolbar should contain the focus"
+ );
+
+ // Now move focus elsewhere.
+ EventUtils.synthesizeKey("KEY_Tab", {}, window);
+ activeElement = document.activeElement;
+ Assert.ok(
+ !mailButton.matches(":focus"),
+ "Mail button should no longer be focused"
+ );
+ Assert.ok(
+ !spacesToolbar.matches(":focus-within"),
+ "Spaces toolbar should no longer contain the focus"
+ );
+
+ // Hide the spaces toolbar using the app menu.
+ await toggleVisibilityWithAppMenu(true);
+ await assertVisibility(false, "after hiding with the app menu");
+
+ // macOS by default doesn't move the focus when clicking on toolbar buttons.
+ if (AppConstants.platform != "macosx") {
+ Assert.notEqual(
+ document.activeElement,
+ activeElement,
+ "The focus moved from the previous element"
+ );
+ // Focus should be on the main app menu since we used the mouse to toggle the
+ // spaces toolbar.
+ Assert.equal(
+ document.activeElement,
+ document.getElementById("button-appmenu"),
+ "Active element is on the app menu"
+ );
+ } else {
+ Assert.equal(
+ document.activeElement,
+ activeElement,
+ "The focus didn't move from the previous element"
+ );
+ }
+
+ // Now click the status bar toggle button to reveal the toolbar again.
+ toggleButton.focus();
+ Assert.ok(
+ toggleButton.matches(":focus"),
+ "Toggle button should be focusable"
+ );
+ EventUtils.synthesizeKey("KEY_Enter", {}, window);
+ await assertVisibility(true, "after showing with the toggle button");
+ // Focus is restored to the mailButton.
+ Assert.ok(
+ mailButton.matches(":focus"),
+ "Mail button should become focused again"
+ );
+
+ // Clicked buttons open or move to the correct tab, starting with just one tab
+ // open.
+ await sub_test_cycle_through_primary_tabs();
+});
+
+add_task(async function testSpacesToolbarContextMenu() {
+ let tabmail = document.getElementById("tabmail");
+ let firstMailTabInfo = tabmail.currentTabInfo;
+ firstMailTabInfo.folder = folderB;
+
+ // Fetch context menu elements.
+ let contextMenu = document.getElementById("spacesContextMenu");
+ let newTabItem = document.getElementById("spacesContextNewTabItem");
+ let newWindowItem = document.getElementById("spacesContextNewWindowItem");
+
+ let settingsMenu = document.getElementById("settingsContextMenu");
+ let settingsItem = document.getElementById("settingsContextOpenSettingsItem");
+ let accountItem = document.getElementById(
+ "settingsContextOpenAccountSettingsItem"
+ );
+ let addonsItem = document.getElementById("settingsContextOpenAddonsItem");
+
+ /**
+ * Open the context menu, test its state, select an action and wait for it to
+ * close.
+ *
+ * @param {object} input - Input data.
+ * @param {Element} input.button - The button whose context menu should be
+ * opened.
+ * @param {Element} [input.item] - The context menu item to select. Either
+ * this or switchItem must be given.
+ * @param {number} [input.switchItem] - The nth switch-to-tab item to select.
+ * @param {object} expect - The expected state of the context menu when
+ * opened.
+ * @param {boolean} [expect.settings=false] - Whether we expect the settings
+ * context menu. If this is true, the other values are ignored.
+ * @param {boolean} [expect.newTab=false] - Whether we expect the "Open in new
+ * tab" item to be visible.
+ * @param {boolean} [expect.newWindow=false] - Whether we expect the "Open in
+ * new window" item to be visible.
+ * @param {number} [expect.numSwitch=0] - The expected number of switch-to-tab
+ * items.
+ * @param {string} msg - A message to use in tests.
+ */
+ async function useContextMenu(input, expect, msg) {
+ let menu = expect.settings ? settingsMenu : contextMenu;
+ let shownPromise = BrowserTestUtils.waitForEvent(menu, "popupshown");
+ EventUtils.synthesizeMouseAtCenter(
+ input.button,
+ { type: "contextmenu" },
+ window
+ );
+ await shownPromise;
+ let item = input.item;
+ if (!expect.settings) {
+ Assert.equal(
+ BrowserTestUtils.is_visible(newTabItem),
+ expect.newTab || false,
+ `Open in new tab item visibility: ${msg}`
+ );
+ Assert.equal(
+ BrowserTestUtils.is_visible(newWindowItem),
+ expect.newWindow || false,
+ `Open in new window item visibility: ${msg}`
+ );
+ let switchItems = menu.querySelectorAll(".switch-to-tab");
+ Assert.equal(
+ switchItems.length,
+ expect.numSwitch || 0,
+ `Should have the expected number of switch items: ${msg}`
+ );
+ if (!item) {
+ item = switchItems[input.switchItem];
+ }
+ }
+ let hiddenPromise = BrowserTestUtils.waitForEvent(menu, "popuphidden");
+ menu.activateItem(item);
+ await hiddenPromise;
+ }
+
+ let tabScroll = document.getElementById("tabmail-arrowscrollbox").scrollbox;
+ /**
+ * Ensure the tab is scrolled into view.
+ *
+ * @param {MozTabmailTab} - The tab to scroll into view.
+ */
+ async function scrollToTab(tab) {
+ function tabInView() {
+ let tabRect = tab.getBoundingClientRect();
+ let scrollRect = tabScroll.getBoundingClientRect();
+ return (
+ tabRect.left >= scrollRect.left && tabRect.right <= scrollRect.right
+ );
+ }
+ if (tabInView()) {
+ info(`Tab ${tab.label} already in view`);
+ return;
+ }
+ tab.scrollIntoView();
+ await TestUtils.waitForCondition(
+ tabInView,
+ "Tab should be scrolled into view: " + tab.label
+ );
+ info(`Tab ${tab.label} was scrolled into view`);
+ }
+
+ let numTabs = 0;
+ /**
+ * Wait for and return the latest tab.
+ *
+ * This should be called every time a tab is created so the test can keep
+ * track of the expected number of tabs.
+ *
+ * @returns {MozTabmailTab} - The last tab.
+ */
+ async function waitForNewTab() {
+ numTabs++;
+ let tabs;
+ await TestUtils.waitForCondition(() => {
+ tabs = document.querySelectorAll("tab.tabmail-tab");
+ return tabs.length == numTabs;
+ }, `Waiting for ${numTabs} tabs`);
+ return tabs[numTabs - 1];
+ }
+
+ /**
+ * Close a tab and wait for it to close.
+ *
+ * This should be used alongside waitForNewTab so the test can keep track of
+ * the expected number of tabs.
+ *
+ * @param {MozTabmailTab} - The tab to close.
+ */
+ async function closeTab(tab) {
+ numTabs--;
+ await scrollToTab(tab);
+ EventUtils.synthesizeMouseAtCenter(
+ tab.querySelector(".tab-close-button"),
+ {},
+ window
+ );
+ await TestUtils.waitForCondition(
+ () => document.querySelectorAll("tab.tabmail-tab").length == numTabs,
+ "Waiting for tab to close"
+ );
+ }
+
+ let toolbar = document.getElementById("spacesToolbar");
+ /**
+ * Verify the current tab and space match.
+ *
+ * @param {MozTabmailTab} tab - The expected tab.
+ * @param {Element} spaceButton - The expected button to be shown as the
+ * current space in the spaces toolbar.
+ * @param {string} msg - A message to use in tests.
+ */
+ async function assertTab(tab, spaceButton, msg) {
+ await TestUtils.waitForCondition(
+ () => tab.selected,
+ `Tab should be selected: ${msg}`
+ );
+ let current = toolbar.querySelectorAll("button.current");
+ Assert.equal(current.length, 1, `Should have one current space: ${msg}`);
+ Assert.equal(
+ current[0],
+ spaceButton,
+ `Current button ${current[0].id} should match: ${msg}`
+ );
+ }
+
+ /**
+ * Click on a tab and verify we have switched tabs and spaces.
+ *
+ * @param {MozTabmailTab} tab - The tab to click.
+ * @param {Element} spaceButton - The expected button to be shown as the
+ * current space after clicking the tab.
+ * @param {string} msg - A message to use in tests.
+ */
+ async function switchTab(tab, spaceButton, msg) {
+ await scrollToTab(tab);
+ EventUtils.synthesizeMouseAtCenter(tab, {}, window);
+ await assertTab(tab, spaceButton, msg);
+ }
+
+ // -- Test initial tab --
+
+ let mailButton = document.getElementById("mailButton");
+ let firstTab = await waitForNewTab();
+ await assertTab(firstTab, mailButton, "First tab is mail tab");
+ await assertMailShown();
+
+ // -- Test spaces that only open one tab --
+
+ let calendarTab;
+ let calendarButton = document.getElementById("calendarButton");
+ for (let { name, button, assertShown } of [
+ {
+ name: "address book",
+ button: document.getElementById("addressBookButton"),
+ assertShown: assertAddressBookShown,
+ },
+ {
+ name: "calendar",
+ button: calendarButton,
+ assertShown: assertCalendarShown,
+ },
+ {
+ name: "tasks",
+ button: document.getElementById("tasksButton"),
+ assertShown: assertTasksShown,
+ },
+ {
+ name: "chat",
+ button: document.getElementById("chatButton"),
+ assertShown: assertChatShown,
+ },
+ ]) {
+ info(`Testing ${name} space`);
+ // Only have option to open in new tab.
+ await useContextMenu(
+ { button, item: newTabItem },
+ { newTab: true },
+ `Opening ${name} tab`
+ );
+ let newTab = await waitForNewTab();
+ if (name == "calendar") {
+ calendarTab = newTab;
+ }
+ await assertTab(newTab, button, `Opened ${name} tab`);
+ await assertShown();
+ // Only have option to switch tabs.
+ // Doing this from the same tab does nothing.
+ await useContextMenu(
+ { button, switchItem: 0 },
+ { numSwitch: 1 },
+ `When ${name} tab is open`
+ );
+ // Wait one tick to allow tabs to potentially change.
+ await TestUtils.waitForTick();
+ // However, the same tab should remain shown.
+ await assertShown();
+
+ // Switch to first tab and back.
+ await switchTab(firstTab, mailButton, `${name} to first tab`);
+ await assertMailShown();
+ await useContextMenu(
+ { button, switchItem: 0 },
+ { numSwitch: 1 },
+ `Switching from first tab to ${name}`
+ );
+ await assertTab(newTab, button, `Switched from first tab to ${name}`);
+ await assertShown();
+ }
+
+ // -- Test opening mail space in a new tab --
+
+ // Open new mail tabs whilst we are still in a non-mail tab.
+ await useContextMenu(
+ { button: mailButton, item: newTabItem },
+ { newWindow: true, newTab: true, numSwitch: 1 },
+ "Opening the second mail tab"
+ );
+ let secondMailTab = await waitForNewTab();
+ await assertTab(secondMailTab, mailButton, "Opened second mail tab");
+ await assertMailShown();
+ // Displayed folder should be the same as in the first mail tab.
+ let [, secondMailTabInfo] =
+ tabmail._getTabContextForTabbyThing(secondMailTab);
+ await TestUtils.waitForCondition(
+ () => secondMailTabInfo.folder?.URI == folderB.URI,
+ "Should display folder B in the second mail tab"
+ );
+
+ secondMailTabInfo.folder = folderA;
+
+ // Open a new mail tab whilst in a mail tab.
+ await useContextMenu(
+ { button: mailButton, item: newTabItem },
+ { newWindow: true, newTab: true, numSwitch: 2 },
+ "Opening the third mail tab"
+ );
+ let thirdMailTab = await waitForNewTab();
+ await assertTab(thirdMailTab, mailButton, "Opened third mail tab");
+ await assertMailShown();
+ // Displayed folder should be the same as in the mail tab that was in view
+ // when the context menu was opened, rather than the folder in the first tab.
+ let [, thirdMailTabInfo] = tabmail._getTabContextForTabbyThing(thirdMailTab);
+ await TestUtils.waitForCondition(
+ () => thirdMailTabInfo.folder?.URI == folderA.URI,
+ "Should display folder A in the third mail tab"
+ );
+
+ // -- Test switching between the multiple mail tabs --
+
+ await useContextMenu(
+ { button: mailButton, switchItem: 1 },
+ { newWindow: true, newTab: true, numSwitch: 3 },
+ "Switching to second mail tab"
+ );
+ await assertTab(secondMailTab, mailButton, "Switch to second mail tab");
+ await assertMailShown();
+ await useContextMenu(
+ { button: mailButton, switchItem: 0 },
+ { newWindow: true, newTab: true, numSwitch: 3 },
+ "Switching to first mail tab"
+ );
+ await assertTab(firstTab, mailButton, "Switch to first mail tab");
+ await assertMailShown();
+
+ await switchTab(calendarTab, calendarButton, "First mail to calendar tab");
+ await useContextMenu(
+ { button: mailButton, switchItem: 2 },
+ { newWindow: true, newTab: true, numSwitch: 3 },
+ "Switching to third mail tab"
+ );
+ await assertTab(thirdMailTab, mailButton, "Switch to third mail tab");
+ await assertMailShown();
+
+ // -- Test the mail button with multiple mail tabs --
+
+ // Clicking the mail button whilst in the mail space does nothing.
+ // Specifically, we do not want it to take us to the first tab.
+ EventUtils.synthesizeMouseAtCenter(mailButton, {}, window);
+ // Wait one cycle to see if the tab would change.
+ await TestUtils.waitForTick();
+ await assertTab(thirdMailTab, mailButton, "Remain in third tab");
+ await assertMailShown();
+ Assert.equal(
+ thirdMailTabInfo.folder.URI,
+ folderA.URI,
+ "Still display folder A in the third mail tab"
+ );
+
+ // Clicking the mail button whilst in a different space takes us to the first
+ // mail tab.
+ await switchTab(calendarTab, calendarButton, "Third mail to calendar tab");
+ EventUtils.synthesizeMouseAtCenter(mailButton, {}, window);
+ await assertTab(firstTab, mailButton, "Switch to the first mail tab");
+ await assertMailShown();
+ Assert.equal(
+ firstMailTabInfo.folder.URI,
+ folderB.URI,
+ "Still display folder B in the first mail tab"
+ );
+
+ // -- Test opening the mail space in a new window --
+
+ let windowPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
+ await useContextMenu(
+ { button: mailButton, item: newWindowItem },
+ { newWindow: true, newTab: true, numSwitch: 3 },
+ "Opening mail tab in new window"
+ );
+ let newMailWindow = await windowPromise;
+ let newTabmail = newMailWindow.document.getElementById("tabmail");
+ // Expect the same folder as the previously focused tab.
+ await TestUtils.waitForCondition(
+ () => newTabmail.currentTabInfo.folder?.URI == folderB.URI,
+ "Waiting for folder B to be displayed in the new window"
+ );
+ Assert.equal(
+ newMailWindow.document.querySelectorAll("tab.tabmail-tab").length,
+ 1,
+ "Should only have one tab in the new window"
+ );
+ await assertMailShown(newMailWindow);
+
+ // -- Test opening different tabs that belong to the settings space --
+
+ let settingsButton = document.getElementById("settingsButton");
+ await useContextMenu(
+ { button: settingsButton, item: accountItem },
+ { settings: true },
+ "Opening account settings"
+ );
+ let accountTab = await waitForNewTab();
+ // Shown as part of the settings space.
+ await assertTab(accountTab, settingsButton, "Opened account settings tab");
+ await assertContentShown("about:accountsettings");
+
+ await useContextMenu(
+ { button: settingsButton, item: settingsItem },
+ { settings: true },
+ "Opening settings"
+ );
+ let settingsTab = await waitForNewTab();
+ // Shown as part of the settings space.
+ await assertTab(settingsTab, settingsButton, "Opened settings tab");
+ await assertSettingsShown();
+
+ await useContextMenu(
+ { button: settingsButton, item: addonsItem },
+ { settings: true },
+ "Opening add-ons"
+ );
+ let addonsTab = await waitForNewTab();
+ // Shown as part of the settings space.
+ await assertTab(addonsTab, settingsButton, "Opened add-ons tab");
+ await assertContentShown("about:addons");
+
+ // -- Test the settings button with multiple settings tabs --
+
+ // Clicking the settings button whilst in the settings space does nothing.
+ EventUtils.synthesizeMouseAtCenter(settingsButton, {}, window);
+ // Wait one cycle to see if the tab would change.
+ await TestUtils.waitForTick();
+ await assertTab(addonsTab, settingsButton, "Remain in add-ons tab");
+ await assertContentShown("about:addons");
+
+ // Clicking the settings button whilst in a different space takes us to the
+ // settings tab, rather than the first tab, since this is the primary tab for
+ // the space.
+ await switchTab(calendarTab, calendarButton, "Add-ons to calendar tab");
+ EventUtils.synthesizeMouseAtCenter(settingsButton, {}, window);
+ await assertTab(settingsTab, settingsButton, "Switch to the settings tab");
+ await assertSettingsShown();
+
+ // Clicking the settings button whilst in a different space and no settings
+ // tab will open a new settings tab, rather than switch to another tab in the
+ // settings space because they are not the primary tab for the space.
+ await closeTab(settingsTab);
+ await switchTab(calendarTab, calendarButton, "Settings to calendar tab");
+ EventUtils.synthesizeMouseAtCenter(settingsButton, {}, window);
+ settingsTab = await waitForNewTab();
+ await assertTab(settingsTab, settingsButton, "Re-opened settings tab");
+ await assertSettingsShown();
+
+ // -- Test opening different settings tabs when they already exist --
+
+ await useContextMenu(
+ { button: settingsButton, item: addonsItem },
+ { settings: true },
+ "Switching to add-ons"
+ );
+ await assertTab(addonsTab, settingsButton, "Switched to add-ons");
+ await assertContentShown("about:addons");
+
+ await useContextMenu(
+ { button: settingsButton, item: accountItem },
+ { settings: true },
+ "Switching to account settings"
+ );
+ await assertTab(accountTab, settingsButton, "Switched to account settings");
+ await assertContentShown("about:accountsettings");
+
+ await useContextMenu(
+ { button: settingsButton, item: settingsItem },
+ { settings: true },
+ "Switching to settings"
+ );
+ await assertTab(settingsTab, settingsButton, "Switched to settings");
+ await assertSettingsShown();
+
+ // -- Test clicking the spaces buttons when all the tabs are already open.
+
+ await sub_test_cycle_through_primary_tabs();
+
+ // Tidy up the opened window.
+ // FIXME: Closing the window earlier in the test causes a test failure on the
+ // osx build on the try server.
+ await BrowserTestUtils.closeWindow(newMailWindow);
+});
+
+add_task(async function testSpacesToolbarMenubar() {
+ document.getElementById("toolbar-menubar").removeAttribute("autohide");
+ let spacesToolbar = document.getElementById("spacesToolbar");
+
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("collapseButton"),
+ {},
+ window
+ );
+ Assert.ok(spacesToolbar.hidden, "The spaces toolbar is hidden");
+ Assert.ok(
+ !document.getElementById("spacesToolbarReveal").hidden,
+ "The status bar toggle button is visible"
+ );
+
+ // Test the menubar button.
+ let viewShownPromise = BrowserTestUtils.waitForEvent(
+ document.getElementById("menu_View_Popup"),
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("menu_View"),
+ {},
+ window
+ );
+ await viewShownPromise;
+
+ let toolbarsShownPromise = BrowserTestUtils.waitForEvent(
+ document.getElementById("view_toolbars_popup"),
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("menu_Toolbars"),
+ {},
+ window
+ );
+ await toolbarsShownPromise;
+
+ let menuButton = document.getElementById("viewToolbarsPopupSpacesToolbar");
+ Assert.ok(
+ menuButton.getAttribute("checked") != "true",
+ "The menu item is not checked"
+ );
+
+ let viewHiddenPromise = BrowserTestUtils.waitForEvent(
+ document.getElementById("menu_View_Popup"),
+ "popuphidden"
+ );
+ EventUtils.synthesizeMouseAtCenter(menuButton, {}, window);
+ await viewHiddenPromise;
+
+ Assert.ok(
+ menuButton.getAttribute("checked") == "true",
+ "The menu item is checked"
+ );
+}).__skipMe = AppConstants.platform == "macosx"; // Can't click menu bar on Mac.
+
+add_task(async function testSpacesToolbarOSX() {
+ let size = document
+ .getElementById("spacesToolbar")
+ .getBoundingClientRect().width;
+
+ // By default, macOS shouldn't need any custom styling.
+ Assert.ok(
+ !document.getElementById("titlebar").hasAttribute("style"),
+ "The custom styling was cleared from all toolbars"
+ );
+
+ let styleAppliedPromise = BrowserTestUtils.waitForCondition(
+ () =>
+ document.getElementById("tabmail-tabs").getAttribute("style") ==
+ `margin-inline-start: ${size}px;`,
+ "The correct style was applied to the tabmail"
+ );
+
+ // Force full screen.
+ window.fullScreen = true;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ await styleAppliedPromise;
+
+ let styleRemovedPromise = BrowserTestUtils.waitForCondition(
+ () => !document.getElementById("tabmail-tabs").hasAttribute("style"),
+ "The custom styling was cleared from all toolbars"
+ );
+ // Restore original window size.
+ window.fullScreen = false;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ await styleRemovedPromise;
+}).__skipMe = AppConstants.platform != "macosx";
+
+add_task(async function testSpacesToolbarClearedAlignment() {
+ // Hide the spaces toolbar to check if the style it's cleared.
+ window.gSpacesToolbar.toggleToolbar(true);
+ Assert.ok(
+ !document.getElementById("titlebar").hasAttribute("style") &&
+ !document.getElementById("navigation-toolbox").hasAttribute("style"),
+ "The custom styling was cleared from all toolbars"
+ );
+});
+
+add_task(async function testSpacesToolbarExtension() {
+ window.gSpacesToolbar.toggleToolbar(false);
+
+ for (let i = 0; i < 6; i++) {
+ await window.gSpacesToolbar.createToolbarButton(`testButton${i}`, {
+ title: `Title ${i}`,
+ url: `https://test.invalid/${i}`,
+ iconStyles: new Map([
+ [
+ "--webextension-toolbar-image",
+ 'url("chrome://messenger/content/extension.svg")',
+ ],
+ ]),
+ });
+ let button = document.getElementById(`testButton${i}`);
+ Assert.ok(button);
+ Assert.equal(button.title, `Title ${i}`);
+
+ let img = button.querySelector("img");
+ Assert.equal(
+ img.style.getPropertyValue("--webextension-toolbar-image"),
+ `url("chrome://messenger/content/extension.svg")`,
+ `Button image should have the correct icon.`
+ );
+
+ let menuitem = document.getElementById(`testButton${i}-menuitem`);
+ Assert.ok(menuitem);
+ Assert.equal(menuitem.label, `Title ${i}`);
+ Assert.equal(
+ menuitem.style.getPropertyValue("--webextension-toolbar-image"),
+ `url("chrome://messenger/content/extension.svg")`,
+ `Menuitem should have the correct icon.`
+ );
+
+ let space = window.gSpacesToolbar.spaces.find(
+ space => space.name == `testButton${i}`
+ );
+ Assert.ok(space);
+ Assert.equal(
+ space.url,
+ `https://test.invalid/${i}`,
+ "Added url should be correct."
+ );
+ }
+
+ for (let i = 0; i < 6; i++) {
+ await window.gSpacesToolbar.updateToolbarButton(`testButton${i}`, {
+ title: `Modified Title ${i}`,
+ url: `https://test.invalid/${i + 1}`,
+ iconStyles: new Map([
+ [
+ "--webextension-toolbar-image",
+ 'url("chrome://messenger/skin/icons/new-addressbook.svg")',
+ ],
+ ]),
+ });
+ let button = document.getElementById(`testButton${i}`);
+ Assert.ok(button);
+ Assert.equal(button.title, `Modified Title ${i}`);
+
+ let img = button.querySelector("img");
+ Assert.equal(
+ img.style.getPropertyValue("--webextension-toolbar-image"),
+ `url("chrome://messenger/skin/icons/new-addressbook.svg")`,
+ `Button image should have the correct icon.`
+ );
+
+ let menuitem = document.getElementById(`testButton${i}-menuitem`);
+ Assert.ok(menuitem);
+ Assert.equal(
+ menuitem.label,
+ `Modified Title ${i}`,
+ "Updated title should be correct."
+ );
+ Assert.equal(
+ menuitem.style.getPropertyValue("--webextension-toolbar-image"),
+ `url("chrome://messenger/skin/icons/new-addressbook.svg")`,
+ `Menuitem should have the correct icon.`
+ );
+
+ let space = window.gSpacesToolbar.spaces.find(
+ space => space.name == `testButton${i}`
+ );
+ Assert.ok(space);
+ Assert.equal(
+ space.url,
+ `https://test.invalid/${i + 1}`,
+ "Updated url should be correct."
+ );
+ }
+
+ let overflowButton = document.getElementById(
+ "spacesToolbarAddonsOverflowButton"
+ );
+
+ let originalHeight = window.outerHeight;
+ // Set a ridiculous tiny height to be sure all add-on buttons are hidden.
+ window.resizeTo(window.outerWidth, 300);
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ await BrowserTestUtils.waitForCondition(
+ () => !overflowButton.hidden,
+ "The overflow button is visible"
+ );
+
+ let overflowPopup = document.getElementById("spacesToolbarAddonsPopup");
+ let popupshown = BrowserTestUtils.waitForEvent(overflowPopup, "popupshown");
+ overflowButton.click();
+ await popupshown;
+
+ Assert.ok(overflowPopup.hasChildNodes());
+
+ let popuphidden = BrowserTestUtils.waitForEvent(overflowPopup, "popuphidden");
+ // Restore the original height.
+ window.resizeTo(window.outerWidth, originalHeight);
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ await popuphidden;
+ await BrowserTestUtils.waitForCondition(
+ () => overflowButton.hidden,
+ "The overflow button is hidden"
+ );
+
+ // Remove all previously added toolbar buttons and make sure all previously
+ // generate elements are properly cleared.
+ for (let i = 0; i < 6; i++) {
+ await window.gSpacesToolbar.removeToolbarButton(`testButton${i}`);
+ let space = window.gSpacesToolbar.spaces.find(
+ space => space.name == `testButton${i}`
+ );
+ Assert.ok(!space);
+
+ let button = document.getElementById(`testButton${i}`);
+ Assert.ok(!button);
+
+ let menuitem = document.getElementById(`testButton${i}-menuitem`);
+ Assert.ok(!menuitem);
+ }
+});
+
+add_task(function testPinnedSpacesBadge() {
+ window.gSpacesToolbar.toggleToolbar(true);
+ let spacesPinnedButton = document.getElementById("spacesPinnedButton");
+ let spacesPopupButtonChat = document.getElementById("spacesPopupButtonChat");
+
+ window.gSpacesToolbar.updatePinnedBadgeState();
+
+ Assert.ok(
+ !spacesPinnedButton.classList.contains("has-badge"),
+ "Pinned button does not indicate badged items without any"
+ );
+
+ spacesPopupButtonChat.classList.add("has-badge");
+ window.gSpacesToolbar.updatePinnedBadgeState();
+
+ Assert.ok(
+ spacesPinnedButton.classList.contains("has-badge"),
+ "Pinned button indicates it has badged items"
+ );
+
+ spacesPopupButtonChat.classList.remove("has-badge");
+ window.gSpacesToolbar.updatePinnedBadgeState();
+
+ Assert.ok(
+ !spacesPinnedButton.classList.contains("has-badge"),
+ "Badge state is reset from pinned button"
+ );
+});
+
+add_task(async function testSpacesToolbarFocusRing() {
+ // Make sure the spaces toolbar is visible.
+ window.gSpacesToolbar.toggleToolbar(false);
+ // Move the focus ring on the mail toolbar button.
+ document.getElementById("mailButton").focus();
+
+ // Collect an array of all currently visible buttons.
+ let buttons = [
+ ...document.querySelectorAll(".spaces-toolbar-button:not([hidden])"),
+ ];
+
+ // Simulate the Arrow Down keypress to make sure the correct button gets the
+ // focus.
+ for (let i = 1; i < buttons.length; i++) {
+ let previousElement = document.activeElement;
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ Assert.equal(
+ document.activeElement.id,
+ buttons[i].id,
+ "The next button is focused"
+ );
+ Assert.ok(
+ document.activeElement.tabIndex == 0 && previousElement.tabIndex == -1,
+ "The roving tab index was updated"
+ );
+ }
+
+ // Do the same with the Arrow Up key press but reversing the array.
+ buttons.reverse();
+ for (let i = 1; i < buttons.length; i++) {
+ let previousElement = document.activeElement;
+ EventUtils.synthesizeKey("KEY_ArrowUp", {}, window);
+ Assert.equal(
+ document.activeElement.id,
+ buttons[i].id,
+ "The previous button is focused"
+ );
+ Assert.ok(
+ document.activeElement.tabIndex == 0 && previousElement.tabIndex == -1,
+ "The roving tab index was updated"
+ );
+ }
+
+ // Pressing the END key should move the focus down to the last available
+ // button.
+ EventUtils.synthesizeKey("KEY_End", {}, window);
+ Assert.equal(
+ document.activeElement.id,
+ "collapseButton",
+ "The last button is focused"
+ );
+
+ // Pressing the HOME key should move the focus up to the first available
+ // button.
+ EventUtils.synthesizeKey("KEY_Home", {}, window);
+ Assert.equal(
+ document.activeElement.id,
+ "mailButton",
+ "The first button is focused"
+ );
+
+ // Focus follows the mouse click.
+ EventUtils.synthesizeMouseAtCenter(
+ document.getElementById("calendarButton"),
+ {},
+ window
+ );
+ Assert.equal(
+ document.activeElement.id,
+ "calendarButton",
+ "Focus should move to the clicked calendar button"
+ );
+
+ // Now press a key to make sure roving index was updated with the click.
+ EventUtils.synthesizeKey("KEY_ArrowDown", {}, window);
+ Assert.equal(
+ document.activeElement.id,
+ "tasksButton",
+ "Focus should move to the tasks button"
+ );
+});