summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/unifiedtoolbar/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/unifiedtoolbar/test/browser')
-rw-r--r--comm/mail/components/unifiedtoolbar/test/browser/browser.ini16
-rw-r--r--comm/mail/components/unifiedtoolbar/test/browser/browser_customizableItems.js173
-rw-r--r--comm/mail/components/unifiedtoolbar/test/browser/browser_searchBar.js263
-rw-r--r--comm/mail/components/unifiedtoolbar/test/browser/browser_toolbarMigration.js99
-rw-r--r--comm/mail/components/unifiedtoolbar/test/browser/browser_unifiedToolbarTab.js285
-rw-r--r--comm/mail/components/unifiedtoolbar/test/browser/files/searchBar.xhtml21
-rw-r--r--comm/mail/components/unifiedtoolbar/test/browser/files/unifiedToolbarTab.xhtml22
7 files changed, 879 insertions, 0 deletions
diff --git a/comm/mail/components/unifiedtoolbar/test/browser/browser.ini b/comm/mail/components/unifiedtoolbar/test/browser/browser.ini
new file mode 100644
index 0000000000..072a4571ef
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/test/browser/browser.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+prefs =
+ mail.provider.suppress_dialog_on_startup=true
+ mail.spellcheck.inline=false
+ mail.spotlight.firstRunDone=true
+ mail.winsearch.firstRunDone=true
+ mailnews.database.global.indexer.enabled=false
+ mailnews.start_page.override_url=about:blank
+ mailnews.start_page.url=about:blank
+subsuite = thunderbird
+support-files = files/**
+
+[browser_customizableItems.js]
+[browser_searchBar.js]
+[browser_toolbarMigration.js]
+[browser_unifiedToolbarTab.js]
diff --git a/comm/mail/components/unifiedtoolbar/test/browser/browser_customizableItems.js b/comm/mail/components/unifiedtoolbar/test/browser/browser_customizableItems.js
new file mode 100644
index 0000000000..152ade47f3
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/test/browser/browser_customizableItems.js
@@ -0,0 +1,173 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ getAvailableItemIdsForSpace,
+ getDefaultItemIdsForSpace,
+ registerExtension,
+ unregisterExtension,
+} = ChromeUtils.importESModule("resource:///modules/CustomizableItems.sys.mjs");
+
+add_task(async function test_extensionRegisterUnregisterDefault() {
+ const extensionId = "thunderbird-compact-light@mozilla.org";
+ await registerExtension(extensionId);
+
+ const itemId = `ext-${extensionId}`;
+ ok(
+ getAvailableItemIdsForSpace("mail").includes(itemId),
+ "Extension item available in mail space"
+ );
+ ok(
+ getDefaultItemIdsForSpace("mail").includes(itemId),
+ "Extension item in mail space by default"
+ );
+ ok(
+ !getAvailableItemIdsForSpace().includes(itemId),
+ "Extension item not available in all spaces"
+ );
+
+ unregisterExtension(extensionId);
+
+ ok(
+ !getAvailableItemIdsForSpace("mail").includes(itemId),
+ "Extension item no longer available in mail space"
+ );
+ ok(
+ !getDefaultItemIdsForSpace("mail").includes(itemId),
+ "Extension item not in mail space by default"
+ );
+});
+
+add_task(async function test_extensionRegisterAllSpaces() {
+ const extensionId = "thunderbird-compact-light@mozilla.org";
+ await registerExtension(extensionId, []);
+
+ const itemId = `ext-${extensionId}`;
+ ok(
+ getAvailableItemIdsForSpace().includes(itemId),
+ "Extension item available in all spaces"
+ );
+ ok(
+ getDefaultItemIdsForSpace("default").includes(itemId),
+ "Extension item in all spaces by default"
+ );
+ ok(
+ !getAvailableItemIdsForSpace("mail").includes(itemId),
+ "Extension item not available in mail space"
+ );
+ ok(
+ getDefaultItemIdsForSpace("mail").includes(itemId),
+ "Extension item in mail space by default"
+ );
+
+ unregisterExtension(extensionId);
+
+ ok(
+ !getAvailableItemIdsForSpace().includes(itemId),
+ "Extension item no longer available in all spaces"
+ );
+ ok(
+ !getDefaultItemIdsForSpace("default").includes(itemId),
+ "Extension item not in any space by default"
+ );
+});
+
+add_task(async function test_extensionRegisterMultipleSpaces() {
+ const extensionId = "thunderbird-compact-light@mozilla.org";
+ await registerExtension(extensionId, ["mail", "calendar", "default"]);
+
+ const itemId = `ext-${extensionId}`;
+ ok(
+ getAvailableItemIdsForSpace("calendar").includes(itemId),
+ "Extension item available in calendar space"
+ );
+ ok(
+ getDefaultItemIdsForSpace("calendar").includes(itemId),
+ "Extension item in calendar space by default"
+ );
+ ok(
+ getAvailableItemIdsForSpace("mail").includes(itemId),
+ "Extension item available in mail space"
+ );
+ ok(
+ getDefaultItemIdsForSpace("mail").includes(itemId),
+ "Extension item in mail space by default"
+ );
+ ok(
+ !getAvailableItemIdsForSpace().includes(itemId),
+ "Extension item not available in all spaces"
+ );
+ ok(
+ getAvailableItemIdsForSpace("default").includes(itemId),
+ "Extension item available in default space"
+ );
+ ok(
+ getDefaultItemIdsForSpace("default").includes(itemId),
+ "Extension item in default space"
+ );
+
+ unregisterExtension(extensionId);
+
+ ok(
+ !getAvailableItemIdsForSpace("mail").includes(itemId),
+ "Extension item no longer available in mail space"
+ );
+ ok(
+ !getDefaultItemIdsForSpace("mail").includes(itemId),
+ "Extension item not in mail space by default"
+ );
+ ok(
+ !getAvailableItemIdsForSpace("calendar").includes(itemId),
+ "Extension item no longer available in calendar space"
+ );
+ ok(
+ !getDefaultItemIdsForSpace("calendar").includes(itemId),
+ "Extension item not in calendar space by default"
+ );
+ ok(
+ !getAvailableItemIdsForSpace().includes(itemId),
+ "Extension item not available in all spaces"
+ );
+ ok(
+ !getAvailableItemIdsForSpace("default").includes(itemId),
+ "Extension item not available in default space"
+ );
+ ok(
+ !getDefaultItemIdsForSpace("default").includes(itemId),
+ "Extension item not in default space"
+ );
+});
+
+add_task(async function test_extensionRegisterStableOrder() {
+ const extension1Id = "thunderbird-compact-light@mozilla.org";
+ const extension2Id = "thunderbird-compact-dark@mozilla.org";
+ await registerExtension(extension1Id);
+ await registerExtension(extension2Id);
+
+ const defaultItems = getDefaultItemIdsForSpace("mail");
+
+ const firstExtensionId = defaultItems
+ .find(itemId => itemId.startsWith("ext-"))
+ .slice(4);
+
+ unregisterExtension(firstExtensionId);
+
+ ok(
+ !getDefaultItemIdsForSpace("mail").includes(`ext-${firstExtensionId}`),
+ "Extension that was the first in the default set not in default set"
+ );
+
+ await registerExtension(firstExtensionId);
+
+ Assert.deepEqual(
+ getDefaultItemIdsForSpace("mail"),
+ defaultItems,
+ "Default items order stable for extensions"
+ );
+
+ unregisterExtension(extension1Id);
+ unregisterExtension(extension2Id);
+});
diff --git a/comm/mail/components/unifiedtoolbar/test/browser/browser_searchBar.js b/comm/mail/components/unifiedtoolbar/test/browser/browser_searchBar.js
new file mode 100644
index 0000000000..b88c16f684
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/test/browser/browser_searchBar.js
@@ -0,0 +1,263 @@
+/* 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/. */
+
+let tabmail = document.getElementById("tabmail");
+registerCleanupFunction(() => {
+ tabmail.closeOtherTabs(tabmail.tabInfo[0]);
+});
+let browser;
+let searchBar;
+
+const waitForRender = () => {
+ return new Promise(resolve => {
+ window.requestAnimationFrame(resolve);
+ });
+};
+
+/* These are shadow-root safe variants of the methods in BrowserTestUtils. */
+
+/**
+ * Checks if a DOM element is hidden.
+ *
+ * @param {Element} element
+ * The element which is to be checked.
+ *
+ * @return {boolean}
+ */
+function is_hidden(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none") {
+ return true;
+ }
+ if (style.visibility != "visible") {
+ return true;
+ }
+ if (style.display == "-moz-popup") {
+ return ["hiding", "closed"].includes(element.state);
+ }
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument && element.parentElement) {
+ return is_hidden(element.parentElement);
+ }
+
+ return false;
+}
+
+/**
+ * Checks if a DOM element is visible.
+ *
+ * @param {Element} element
+ * The element which is to be checked.
+ *
+ * @return {boolean}
+ */
+function is_visible(element) {
+ var style = element.ownerGlobal.getComputedStyle(element);
+ if (style.display == "none") {
+ return false;
+ }
+ if (style.visibility != "visible") {
+ return false;
+ }
+ if (style.display == "-moz-popup" && element.state != "open") {
+ return false;
+ }
+
+ // Hiding a parent element will hide all its children
+ if (element.parentNode != element.ownerDocument && element.parentElement) {
+ return is_visible(element.parentElement);
+ }
+
+ return true;
+}
+
+add_setup(async function () {
+ let tab = tabmail.openTab("contentTab", {
+ url: "chrome://mochitests/content/browser/comm/mail/components/unifiedtoolbar/test/browser/files/searchBar.xhtml",
+ });
+
+ await BrowserTestUtils.browserLoaded(tab.browser);
+ tab.browser.focus();
+ browser = tab.browser;
+ searchBar = tab.browser.contentWindow.document.querySelector("search-bar");
+});
+
+add_task(async function test_initialState() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ is(
+ input.getAttribute("aria-label"),
+ searchBar.getAttribute("label"),
+ "Label forwarded to aria-label on input"
+ );
+});
+
+add_task(async function test_labelUpdate() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ searchBar.setAttribute("label", "foo");
+ await waitForRender();
+ is(
+ input.getAttribute("aria-label"),
+ "foo",
+ "Updated label applied to content"
+ );
+});
+
+add_task(async function test_focus() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ searchBar.focus();
+ is(
+ searchBar.shadowRoot.activeElement,
+ input,
+ "Input is focused when search bar is focused"
+ );
+});
+
+add_task(async function test_autocompleteEvent() {
+ const typeAndWaitForAutocomplete = async key => {
+ const eventPromise = BrowserTestUtils.waitForEvent(
+ searchBar,
+ "autocomplete"
+ );
+ await BrowserTestUtils.synthesizeKey(key, {}, browser);
+ return eventPromise;
+ };
+ searchBar.focus();
+ let event = await typeAndWaitForAutocomplete("T");
+ is(event.detail, "T", "Autocomplete for T");
+
+ event = await typeAndWaitForAutocomplete("e");
+ is(event.detail, "Te", "Autocomplete for e");
+
+ event = await typeAndWaitForAutocomplete("KEY_Backspace");
+ is(event.detail, "T", "Autocomplete for backspace");
+
+ await BrowserTestUtils.synthesizeKey("KEY_Backspace", {}, browser);
+});
+
+add_task(async function test_searchEventFromEnter() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ input.value = "Lorem ipsum";
+ searchBar.focus();
+
+ const eventPromise = BrowserTestUtils.waitForEvent(searchBar, "search");
+ await BrowserTestUtils.synthesizeKey("KEY_Enter", {}, browser);
+ const event = await eventPromise;
+
+ is(event.detail, "Lorem ipsum", "Event contains search query");
+ await waitForRender();
+ is(input.value, "", "Input was cleared");
+});
+
+add_task(async function test_searchEventFromButton() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ input.value = "Lorem ipsum";
+
+ const eventPromise = BrowserTestUtils.waitForEvent(searchBar, "search");
+ searchBar.shadowRoot.querySelector("button").click();
+ const event = await eventPromise;
+
+ is(event.detail, "Lorem ipsum", "Event contains search query");
+ await waitForRender();
+ is(input.value, "", "Input was cleared");
+});
+
+add_task(async function test_searchEventPreventDefault() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ input.value = "Lorem ipsum";
+
+ searchBar.addEventListener(
+ "search",
+ event => {
+ event.preventDefault();
+ },
+ {
+ once: true,
+ passive: false,
+ }
+ );
+
+ const eventPromise = BrowserTestUtils.waitForEvent(searchBar, "search");
+ searchBar.shadowRoot.querySelector("button").click();
+ await eventPromise;
+ await waitForRender();
+
+ is(input.value, "Lorem ipsum");
+
+ input.value = "";
+});
+
+add_task(async function test_placeholderVisibility() {
+ const placeholder = searchBar.shadowRoot.querySelector("div");
+ const input = searchBar.shadowRoot.querySelector("input");
+
+ input.value = "";
+ await waitForRender();
+ ok(is_visible(placeholder), "Placeholder is visible initially");
+
+ input.value = "some input";
+ await waitForRender();
+ ok(is_hidden(placeholder), "Placeholder is hidden after text is entered");
+
+ input.value = "";
+ await waitForRender();
+ ok(
+ is_visible(placeholder),
+ "Placeholder is visible again after input is cleared"
+ );
+});
+
+add_task(async function test_placeholderFallbackToLabel() {
+ const placeholder = searchBar.querySelector("span");
+ placeholder.remove();
+
+ const shadowedPlaceholder = searchBar.shadowRoot.querySelector("div");
+ const label = searchBar.getAttribute("label");
+
+ is(
+ shadowedPlaceholder.textContent,
+ label,
+ "Falls back to label if no placeholder slot contents provided"
+ );
+
+ searchBar.setAttribute("label", "Foo bar");
+ is(
+ shadowedPlaceholder.textContent,
+ "Foo bar",
+ "Placeholder contents get updated with label attribute"
+ );
+
+ searchBar.prepend(placeholder);
+ searchBar.setAttribute("label", label);
+});
+
+add_task(async function test_reset() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ const placeholder = searchBar.shadowRoot.querySelector("div");
+ input.value = "Lorem ipsum";
+
+ searchBar.reset();
+
+ is(input.value, "", "Input empty after reset");
+ await waitForRender();
+ ok(is_visible(placeholder), "Placeholder visible");
+});
+
+add_task(async function test_disabled() {
+ const input = searchBar.shadowRoot.querySelector("input");
+ const button = searchBar.shadowRoot.querySelector("button");
+
+ ok(!input.disabled, "Input enabled");
+ ok(!button.disabled, "Button enabled");
+
+ searchBar.setAttribute("disabled", true);
+
+ ok(input.disabled, "Disabled propagated to input");
+ ok(button.disabled, "Disabled propagated to button");
+
+ searchBar.removeAttribute("disabled");
+
+ ok(!input.disabled, "Input enabled again");
+ ok(!button.disabled, "Button enabled again");
+});
diff --git a/comm/mail/components/unifiedtoolbar/test/browser/browser_toolbarMigration.js b/comm/mail/components/unifiedtoolbar/test/browser/browser_toolbarMigration.js
new file mode 100644
index 0000000000..c2ca1147fd
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/test/browser/browser_toolbarMigration.js
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { migrateToolbarForSpace } = ChromeUtils.importESModule(
+ "resource:///modules/ToolbarMigration.sys.mjs"
+);
+const { getState, storeState } = ChromeUtils.importESModule(
+ "resource:///modules/CustomizationState.mjs"
+);
+const { EXTENSION_PREFIX } = ChromeUtils.importESModule(
+ "resource:///modules/CustomizableItems.sys.mjs"
+);
+const { getCachedAllowedSpaces, setCachedAllowedSpaces } = ChromeUtils.import(
+ "resource:///modules/ExtensionToolbarButtons.jsm"
+);
+
+const MESSENGER_WINDOW = "chrome://messenger/content/messenger.xhtml";
+const EXTENSION_ID = "thunderbird-compact-light@mozilla.org";
+
+add_setup(() => {
+ storeState({});
+});
+
+add_task(async function test_migrate_extension() {
+ Services.xulStore.setValue(MESSENGER_WINDOW, "mail-bar3", "currentset", "");
+ Services.xulStore.setValue(
+ MESSENGER_WINDOW,
+ "mail-bar3",
+ "defaultset",
+ "button-getmsg,button-newmsg,separator,button-tag,qfb-show-filter-bar,spring,gloda-search,thunderbird-compact-light_mozilla_org-browserAction-toolbarbutton,button-appmenu"
+ );
+ Services.xulStore.setValue(
+ MESSENGER_WINDOW,
+ "mail-bar3",
+ "extensionset",
+ "thunderbird-compact-light_mozilla_org-browserAction-toolbarbutton"
+ );
+ const extensionPref = Services.prefs.getStringPref(
+ "extensions.webextensions.uuids",
+ ""
+ );
+ const parsedPref = JSON.parse(extensionPref || "{}");
+ if (!parsedPref.hasOwnProperty(EXTENSION_ID)) {
+ parsedPref[EXTENSION_ID] = "foo";
+ Services.prefs.setStringPref(
+ "extensions.webextensions.uuids",
+ JSON.stringify(parsedPref)
+ );
+ }
+
+ migrateToolbarForSpace("mail");
+
+ const newState = getState();
+
+ Assert.deepEqual(
+ newState.mail,
+ [
+ "get-messages",
+ "write-message",
+ "tag-message",
+ "quick-filter-bar",
+ "spacer",
+ `${EXTENSION_PREFIX}${EXTENSION_ID}`,
+ "spacer",
+ ],
+ "Extension button was converted to new ID format"
+ );
+ Assert.ok(
+ !Services.xulStore.hasValue(MESSENGER_WINDOW, "mail-bar3", "currentset"),
+ "Old toolbar state is cleared"
+ );
+ Assert.ok(
+ !Services.xulStore.hasValue(MESSENGER_WINDOW, "mail-bar3", "defaultset"),
+ "Old toolbar default state is cleared"
+ );
+ Assert.ok(
+ !Services.xulStore.hasValue(MESSENGER_WINDOW, "mail-bar3", "extensionset"),
+ "Old toolbar extension state is cleared"
+ );
+ Assert.deepEqual(
+ Object.fromEntries(getCachedAllowedSpaces()),
+ { [EXTENSION_ID]: ["mail"] },
+ "Extension set migrated to new persistent extension state"
+ );
+
+ storeState({});
+ setCachedAllowedSpaces(new Map());
+ if (extensionPref) {
+ Services.prefs.setStringPref(
+ "extensions.webextensions.uuids",
+ extensionPref
+ );
+ } else {
+ Services.prefs.clearUserPref("extensions.webextensions.uuids");
+ }
+});
diff --git a/comm/mail/components/unifiedtoolbar/test/browser/browser_unifiedToolbarTab.js b/comm/mail/components/unifiedtoolbar/test/browser/browser_unifiedToolbarTab.js
new file mode 100644
index 0000000000..336199ee51
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/test/browser/browser_unifiedToolbarTab.js
@@ -0,0 +1,285 @@
+/* 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/. */
+
+let tabmail = document.getElementById("tabmail");
+registerCleanupFunction(() => {
+ tabmail.closeOtherTabs(tabmail.tabInfo[0]);
+});
+let browser;
+let testDocument;
+
+const waitForRender = () => {
+ return new Promise(resolve => {
+ window.requestAnimationFrame(resolve);
+ });
+};
+const getTabButton = tab => tab.shadowRoot.querySelector("button");
+/**
+ * Get the relevant elements for the tab at the given index.
+ *
+ * @param {number} tabIndex
+ * @returns {{tab: UnifiedToolbarTab, button: HTMLButtonElement, pane: HTMLElement}}
+ */
+const getTabElements = tabIndex => {
+ const tab = testDocument.querySelector(
+ `unified-toolbar-tab:nth-child(${tabIndex})`
+ );
+ const button = getTabButton(tab);
+ const pane = tab.pane;
+ return { tab, button, pane };
+};
+
+add_setup(async function () {
+ let tab = tabmail.openTab("contentTab", {
+ url: "chrome://mochitests/content/browser/comm/mail/components/unifiedtoolbar/test/browser/files/unifiedToolbarTab.xhtml",
+ });
+
+ await BrowserTestUtils.browserLoaded(tab.browser);
+ tab.browser.focus();
+ browser = tab.browser;
+ testDocument = tab.browser.contentWindow.document;
+});
+
+add_task(function test_tabElementInitialization() {
+ const activeTab = testDocument.querySelector("unified-toolbar-tab[selected]");
+ is(
+ activeTab.getAttribute("role"),
+ "presentation",
+ "The custom element is just for show"
+ );
+ ok(
+ !activeTab.hasAttribute("aria-controls"),
+ "aria-controls removed from custom element"
+ );
+ ok(activeTab.hasAttribute("selected"), "Active tab kept itself selected");
+ const tabButton = getTabButton(activeTab);
+ is(tabButton.getAttribute("role"), "tab", "Active tab is marked as tab");
+ is(tabButton.tabIndex, 0, "Active tab is in the focus ring");
+ is(
+ tabButton.getAttribute("aria-selected"),
+ "true",
+ "Tab is marked as selected"
+ );
+ ok(
+ tabButton.hasAttribute("aria-controls"),
+ "aria-controls got given to button"
+ );
+
+ const otherTab = testDocument.querySelector(
+ "unified-toolbar-tab:not([selected])"
+ );
+ is(
+ otherTab.getAttribute("role"),
+ "presentation",
+ "The custom element is just for show on the other tab"
+ );
+ ok(
+ !otherTab.hasAttribute("aria-controls"),
+ "aria-controls removed from the other tab"
+ );
+ ok(!otherTab.hasAttribute("selected"), "Other tab didn't select itself");
+ const otherButton = getTabButton(otherTab);
+ is(otherButton.getAttribute("role"), "tab", "Other tab is marked as tab");
+ is(otherButton.tabIndex, -1, "Other tab is not in the focus ring");
+ ok(
+ !otherButton.hasAttribute("aria-selected"),
+ "Other tab isn't marked as selected"
+ );
+ ok(
+ otherButton.hasAttribute("aria-controls"),
+ "aria-controls got given to other button"
+ );
+});
+
+add_task(async function test_paneGetter() {
+ const tab1 = getTabElements(1);
+ const tabPane = testDocument.getElementById("tabPane");
+ const tab2 = getTabElements(2);
+ const otherTabPane = testDocument.getElementById("otherTabPane");
+
+ is(
+ tab1.button.getAttribute("aria-controls"),
+ tabPane.id,
+ "Tab 1 controls tab 1 pane"
+ );
+ is(
+ tab2.button.getAttribute("aria-controls"),
+ otherTabPane.id,
+ "Tab 2 controls tab 2 pane"
+ );
+
+ Assert.strictEqual(
+ tab1.tab.pane,
+ tabPane,
+ "Tab 1 pane getter returns #tabPane"
+ );
+ Assert.strictEqual(
+ tab2.tab.pane,
+ otherTabPane,
+ "Tab 2 pane getter returns #otherTabPane"
+ );
+});
+
+add_task(async function test_unselect() {
+ const tab = getTabElements(1);
+
+ tab.tab.unselect();
+
+ ok(!tab.button.hasAttribute("aria-selected"), "Tab not marked as selected");
+ is(tab.button.tabIndex, -1, "Tab not in focus ring");
+ ok(!tab.tab.hasAttribute("selected"), "Tab not marked selected");
+ ok(tab.pane.hidden, "Tab pane hidden");
+});
+
+add_task(async function test_select() {
+ const tab1 = getTabElements(1);
+ const tab2 = getTabElements(2);
+
+ let tabswitchPromise = BrowserTestUtils.waitForEvent(
+ testDocument.body,
+ "tabswitch"
+ );
+ tab1.tab.select();
+
+ await tabswitchPromise;
+ ok(tab1.tab.hasAttribute("selected"), "Tab 1 selected");
+ is(
+ tab1.button.getAttribute("aria-selected"),
+ "true",
+ "Tab 1 marked as selected"
+ );
+ is(tab1.button.tabIndex, 0, "Tab 1 keyboard selectable");
+ ok(!tab1.pane.hidden, "Tab pane for tab 1 visible");
+
+ tabswitchPromise = BrowserTestUtils.waitForEvent(tab2.tab, "tabswitch");
+ tab2.tab.select();
+
+ await tabswitchPromise;
+ ok(tab2.tab.hasAttribute("selected"), "Tab 2 selected");
+ is(
+ tab2.button.getAttribute("aria-selected"),
+ "true",
+ "Tab 2 has a11y selection"
+ );
+ is(tab2.button.tabIndex, 0, "Tab 2 keyboard selectable");
+ ok(!tab2.pane.hidden, "Tab pane for tab 2 visible");
+
+ ok(!tab1.tab.hasAttribute("selected"), "Tab 1 unselected");
+ ok(!tab1.button.hasAttribute("aria-selected"), "Tab 1 marked as unselected");
+ is(tab1.button.tabIndex, -1, "Tab 1 not in focus ring");
+ ok(tab1.pane.hidden, "Tab pane for tab 1 hidden");
+});
+
+add_task(async function test_switchingTabWithMouse() {
+ const tab1 = getTabElements(1);
+ const tab2 = getTabElements(2);
+
+ tab2.button.click();
+ ok(tab2.tab.hasAttribute("selected"), "Other tab is selected");
+ is(tab2.button.tabIndex, 0, "Other tab is in focus ring");
+ ok(!tab1.tab.hasAttribute("selected"), "First tab is not selected");
+ is(tab1.button.tabIndex, -1, "First tab is not in focus ring");
+ ok(
+ BrowserTestUtils.is_visible(tab2.pane),
+ "Tab pane for selected tab is visible"
+ );
+ ok(BrowserTestUtils.is_hidden(tab1.pane), "Tab pane for first tab is hidden");
+
+ tab1.button.click();
+ ok(tab1.tab.hasAttribute("selected"), "First tab is selected");
+ is(tab1.button.tabIndex, 0, "First tab is in focus ring");
+ ok(!tab2.tab.hasAttribute("selected"), "Other tab is not selected");
+ is(tab2.button.tabIndex, -1, "Other tab is not in focus ring");
+ ok(
+ BrowserTestUtils.is_visible(tab1.pane),
+ "Tab pane for first tab is visible"
+ );
+ ok(BrowserTestUtils.is_hidden(tab2.pane), "Tab pane for other tab is hidden");
+});
+
+add_task(async function test_switchingTabWithKeyboard() {
+ const tab1 = getTabElements(1);
+ const tab2 = getTabElements(2);
+
+ tab1.tab.focus();
+ is(testDocument.activeElement, tab1.tab, "Initially first tab is active");
+
+ await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, browser);
+ is(testDocument.activeElement, tab2.tab, "Second tab is focused");
+ is(
+ tab2.tab.shadowRoot.activeElement,
+ tab2.button,
+ "Button within tab is focused"
+ );
+ await BrowserTestUtils.synthesizeKey(" ", {}, browser);
+ ok(tab2.tab.hasAttribute("selected"), "Other tab is selected");
+ is(tab2.button.tabIndex, 0, "Other tab is in focus ring");
+ ok(!tab1.tab.hasAttribute("selected"), "First tab is not selected");
+ is(tab1.button.tabIndex, -1, "First tab is not in focus ring");
+ ok(
+ BrowserTestUtils.is_visible(tab2.pane),
+ "Tab pane for selected tab is visible"
+ );
+ ok(BrowserTestUtils.is_hidden(tab1.pane), "Tab pane for first tab is hidden");
+
+ await BrowserTestUtils.synthesizeKey("KEY_ArrowLeft", {}, browser);
+ is(testDocument.activeElement, tab1.tab, "Previous tab is selected");
+ await BrowserTestUtils.synthesizeKey("KEY_End", {}, browser);
+ is(testDocument.activeElement, tab2.tab, "Last tab is selected");
+ await BrowserTestUtils.synthesizeKey("KEY_Home", {}, browser);
+ is(testDocument.activeElement, tab1.tab, "First tab is selected");
+ await BrowserTestUtils.synthesizeKey("KEY_Enter", {}, browser);
+ ok(tab1.tab.hasAttribute("selected"), "First tab is selected");
+ is(tab1.button.tabIndex, 0, "First tab is in focus ring");
+ ok(!tab2.tab.hasAttribute("selected"), "Other tab is not selected");
+ is(tab2.button.tabIndex, -1, "Other tab is not in focus ring");
+ ok(
+ BrowserTestUtils.is_visible(tab1.pane),
+ "Tab pane for first tab is visible"
+ );
+ ok(BrowserTestUtils.is_hidden(tab2.pane), "Tab pane for other tab is hidden");
+});
+
+add_task(async function test_switchingTabWithKeyboardRTL() {
+ testDocument.dir = "rtl";
+ await waitForRender();
+ const tab1 = getTabElements(1);
+ const tab2 = getTabElements(2);
+
+ tab1.tab.focus();
+ is(testDocument.activeElement, tab1.tab, "Initially first tab is active");
+
+ await BrowserTestUtils.synthesizeKey("KEY_ArrowLeft", {}, browser);
+ is(testDocument.activeElement, tab2.tab, "Second tab is selected");
+ is(
+ tab2.tab.shadowRoot.activeElement,
+ tab2.button,
+ "Button within tab is focused"
+ );
+ await BrowserTestUtils.synthesizeKey(" ", {}, browser);
+ ok(tab2.tab.hasAttribute("selected"), "Other tab is selected");
+ is(tab2.button.tabIndex, 0, "Other tab is in focus ring");
+ ok(!tab1.tab.hasAttribute("selected"), "First tab is not selected");
+ is(tab1.button.tabIndex, -1, "First tab is not in focus ring");
+ ok(
+ BrowserTestUtils.is_visible(tab2.pane),
+ "Tab pane for selected tab is visible"
+ );
+ ok(BrowserTestUtils.is_hidden(tab1.pane), "Tab pane for first tab is hidden");
+
+ await BrowserTestUtils.synthesizeKey("KEY_ArrowRight", {}, browser);
+ is(testDocument.activeElement, tab1.tab, "Previous tab is selected");
+ await BrowserTestUtils.synthesizeKey("KEY_Enter", {}, browser);
+ ok(tab1.tab.hasAttribute("selected"), "First tab is selected");
+ is(tab1.button.tabIndex, 0, "First tab is in focus ring");
+ ok(!tab2.tab.hasAttribute("selected"), "Other tab is not selected");
+ is(tab2.button.tabIndex, -1, "Other tab is not in focus ring");
+ ok(
+ BrowserTestUtils.is_visible(tab1.pane),
+ "Tab pane for first tab is visible"
+ );
+ ok(BrowserTestUtils.is_hidden(tab2.pane), "Tab pane for other tab is hidden");
+
+ testDocument.dir = "ltr";
+});
diff --git a/comm/mail/components/unifiedtoolbar/test/browser/files/searchBar.xhtml b/comm/mail/components/unifiedtoolbar/test/browser/files/searchBar.xhtml
new file mode 100644
index 0000000000..33000135b4
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/test/browser/files/searchBar.xhtml
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8" />
+ <title>Search bar element test</title>
+ <script type="module" src="chrome://messenger/content/unifiedtoolbar/search-bar.mjs"></script>
+ </head>
+ <body>
+ <template id="searchBarTemplate">
+ <form>
+ <input type="search" placeholder="" required="required"/>
+ <div aria-hidden="true"><slot name="placeholder"></slot></div>
+ <button class="button button-flat icon-button"><slot name="button"></slot></button>
+ </form>
+ </template>
+ <search-bar label="Search">
+ <span slot="placeholder">Placeholder</span>
+ <img slot="button" src="chrome://messenger/skin/icons/new/compact/search.svg" alt="Search"/>
+ </search-bar>
+ </body>
+</html>
diff --git a/comm/mail/components/unifiedtoolbar/test/browser/files/unifiedToolbarTab.xhtml b/comm/mail/components/unifiedtoolbar/test/browser/files/unifiedToolbarTab.xhtml
new file mode 100644
index 0000000000..f30f2b7d8b
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/test/browser/files/unifiedToolbarTab.xhtml
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr">
+ <head>
+ <meta charset="utf-8" />
+ <title>Search bar element test</title>
+ <script type="module" src="chrome://messenger/content/unifiedtoolbar/unified-toolbar-tab.mjs"></script>
+ </head>
+ <body>
+ <template id="unifiedToolbarTabTemplate">
+ <button role="tab">
+ <img alt="" src="" />
+ <slot></slot>
+ </button>
+ </template>
+ <div role="tablist">
+ <unified-toolbar-tab selected="true" aria-controls="tabPane">Tab Title</unified-toolbar-tab>
+ <unified-toolbar-tab aria-controls="otherTabPane">Other Tab</unified-toolbar-tab>
+ </div>
+ <div id="tabPane" role="tabpanel">Panel 1</div>
+ <div id="otherTabPane" role="tabpanel" hidden="hidden">Panel 2</div>
+ </body>
+</html>