diff options
Diffstat (limited to '')
13 files changed, 1648 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> diff --git a/comm/mail/components/unifiedtoolbar/test/unit/test_buttonStyle.js b/comm/mail/components/unifiedtoolbar/test/unit/test_buttonStyle.js new file mode 100644 index 0000000000..0ffeefa00b --- /dev/null +++ b/comm/mail/components/unifiedtoolbar/test/unit/test_buttonStyle.js @@ -0,0 +1,40 @@ +/* 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/. */ + +const { BUTTON_STYLE_MAP, BUTTON_STYLE_PREF } = ChromeUtils.importESModule( + "resource:///modules/ButtonStyle.mjs" +); + +add_task(function test_buttonStyleMap() { + Assert.ok(Array.isArray(BUTTON_STYLE_MAP), "BUTTON_STYLE_MAP is an array"); + Assert.ok( + BUTTON_STYLE_MAP.every(style => typeof style === "string"), + "All entries in the style map should be strings" + ); + for (const style of BUTTON_STYLE_MAP) { + Assert.stringMatches( + style, + /[a-z-]/, + "Button style class should be formatted in kebab case" + ); + } +}); + +add_task(function test_buttonStylePref() { + Assert.equal( + typeof BUTTON_STYLE_PREF, + "string", + "BUTTON_STYLE_PREF is a string" + ); + const prefValue = Services.prefs.getIntPref(BUTTON_STYLE_PREF, 0); + Assert.ok( + Number.isInteger(prefValue), + "BUTTON_STYLE_PREF pref should hold an integer" + ); + Assert.less( + prefValue, + BUTTON_STYLE_MAP.length, + "Value of BUTTON_STYLE_PREF should be within map" + ); +}); diff --git a/comm/mail/components/unifiedtoolbar/test/unit/test_customizableItems.js b/comm/mail/components/unifiedtoolbar/test/unit/test_customizableItems.js new file mode 100644 index 0000000000..55d4f5ba91 --- /dev/null +++ b/comm/mail/components/unifiedtoolbar/test/unit/test_customizableItems.js @@ -0,0 +1,123 @@ +/* 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/. */ + +const { + getAvailableItemIdsForSpace, + getDefaultItemIdsForSpace, + MULTIPLE_ALLOWED_ITEM_IDS, + SKIP_FOCUS_ITEM_IDS, +} = ChromeUtils.importESModule("resource:///modules/CustomizableItems.sys.mjs"); + +const { default: CUSTOMIZABLE_ITEMS } = ChromeUtils.importESModule( + "resource:///modules/CustomizableItemsDetails.mjs" +); + +add_task(function test_getAvailableItemIdsForSpace_anySpace() { + const itemsForAnySpace = getAvailableItemIdsForSpace(); + Assert.ok(Array.isArray(itemsForAnySpace), "returns an array"); + for (const itemId of itemsForAnySpace) { + Assert.equal(typeof itemId, "string", `item ID "${itemId}" is string`); + Assert.greater(itemId.length, 0, `item ID is not empty`); + } +}); + +add_task(function test_getAvailableItemIdsForSpace_emptySpace() { + const itemsForEmptySpace = getAvailableItemIdsForSpace("test"); + Assert.deepEqual(itemsForEmptySpace, [], "Empty array for empty space"); +}); + +add_task(function test_getAvailableItemIdsForSpace_includingAgnostic() { + const items = getAvailableItemIdsForSpace("mail", true); + const itemsForAnySpace = getAvailableItemIdsForSpace(); + const itemsForMailSpace = getAvailableItemIdsForSpace("mail"); + + Assert.ok( + itemsForAnySpace.every(itemId => items.includes(itemId)), + "All space agnostic items are included" + ); + + Assert.ok( + itemsForMailSpace.every(itemId => items.includes(itemId)), + "All mail space items are included" + ); +}); + +add_task(function test_getDefaultItemIdsForSpace_default() { + const items = getDefaultItemIdsForSpace("default"); + + Assert.ok(Array.isArray(items), "Should return an array"); + Assert.deepEqual( + items, + ["spacer", "search-bar", "spacer"], + "Default space should contain the default item set" + ); +}); + +add_task(function test_getDefaultItemIdsForSpace_cloningArray() { + const items1 = getDefaultItemIdsForSpace("default"); + const items2 = getDefaultItemIdsForSpace("default"); + const items3 = getDefaultItemIdsForSpace("mail"); + + Assert.notStrictEqual( + items1, + items2, + "The default sets should be different array instances" + ); + Assert.notStrictEqual( + items2, + items3, + "The second default set an mail space should be different array instances" + ); + Assert.notStrictEqual( + items3, + items1, + "The mail space and first default set should be different array instances" + ); + + Assert.deepEqual( + items1, + items2, + "The two default pseudospace sets should contain the same items" + ); +}); + +add_task(function test_multipleAllowedItemIds() { + Assert.equal( + typeof MULTIPLE_ALLOWED_ITEM_IDS.has, + "function", + "Multiple allowed item IDs should be set-like" + ); + Assert.ok( + Array.from(MULTIPLE_ALLOWED_ITEM_IDS).every( + itemId => typeof itemId === "string" + ), + "Every item in the set should be a string" + ); + for (const item of CUSTOMIZABLE_ITEMS) { + Assert.equal( + MULTIPLE_ALLOWED_ITEM_IDS.has(item.id), + Boolean(item.allowMultiple), + `Set's state should matche the allowMultiple value of ${item.allowMultiple} for ${item.id}` + ); + } +}); + +add_task(function test_skipFocusItemIds() { + Assert.equal( + typeof SKIP_FOCUS_ITEM_IDS.has, + "function", + "Skip focus item IDs should be set-like" + ); + Assert.ok( + Array.from(SKIP_FOCUS_ITEM_IDS).every(itemId => typeof itemId === "string"), + "Every item in the set should be a string" + ); + for (const item of CUSTOMIZABLE_ITEMS) { + Assert.equal( + SKIP_FOCUS_ITEM_IDS.has(item.id), + Boolean(item.skipFocus), + `Set's state should match the skipFocus value of ${item.skipFocus} for ${item.id}` + ); + } +}); diff --git a/comm/mail/components/unifiedtoolbar/test/unit/test_customizableItemsDetails.js b/comm/mail/components/unifiedtoolbar/test/unit/test_customizableItemsDetails.js new file mode 100644 index 0000000000..474e5483ce --- /dev/null +++ b/comm/mail/components/unifiedtoolbar/test/unit/test_customizableItemsDetails.js @@ -0,0 +1,103 @@ +/* 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/. */ + +const { default: CUSTOMIZABLE_ITEMS } = ChromeUtils.importESModule( + "resource:///modules/CustomizableItemsDetails.mjs" +); + +add_task(function test_format() { + for (const item of CUSTOMIZABLE_ITEMS) { + Assert.equal(typeof item, "object", "Customizable item is an object"); + Assert.equal(typeof item.id, "string", `id "${item.id}" is a string`); + Assert.ok(!item.id.includes(","), `id "${item.id}" may not contain commas`); + Assert.greater(item.id.length, 0, `id "${item.id}" is not empty`); + Assert.equal( + typeof item.labelId, + "string", + `labelId is a string for ${item.id}` + ); + Assert.greater( + item.labelId.length, + 0, + `labelId is not empty for ${item.id}` + ); + Assert.ok( + !item.allowMultiple || item.allowMultiple === true, + `allowMultiple is falsy or boolean for ${item.id}` + ); + Assert.ok( + item.spaces === undefined || Array.isArray(item.spaces), + `spaces is undefined or an array for ${item.id}` + ); + if (item.spaces) { + for (const space of item.spaces) { + Assert.equal( + typeof space, + "string", + `space "${space}" expected to be string for ${item.id}` + ); + Assert.greater( + space.length, + 0, + `space is not empty in ${item.id} spaces` + ); + } + } + Assert.ok( + item.templateId === undefined || typeof item.templateId === "string", + `templateId must be undefined or a string for ${item.id}` + ); + if (item.templateId !== undefined) { + Assert.greater( + item.templateId.length, + 0, + `templateId is not empty for ${item.id}` + ); + Assert.ok( + item.requiredModules === undefined || + Array.isArray(item.requiredModules), + `requiredModules is undefined or an array for ${item.id}` + ); + if (item.requiredModules) { + for (const module of item.requiredModules) { + Assert.equal( + typeof module, + "string", + `module "${module}" expected to be string for ${item.id}` + ); + Assert.greater( + module.length, + 0, + `module is not empty in ${item.id} requiredModules` + ); + } + } + } else { + Assert.strictEqual( + item.requiredModules, + undefined, + `requiredModules must not be set because there is no template for item ${item.id}` + ); + } + Assert.ok( + item.hasContextMenu === undefined || + typeof item.hasContextMenu === "boolean", + `hasContextMenu must be undefined or a boolean for ${item.id}` + ); + Assert.ok( + item.skipFocus === undefined || typeof item.skipFocus === "boolean", + `skipFocus must be undefined or a boolean for ${item.id}` + ); + } +}); + +add_task(function test_idsUnique() { + const allIds = CUSTOMIZABLE_ITEMS.map(item => item.id); + const idCounts = allIds.reduce((counts, id) => { + counts[id] = counts[id] ? counts[id] + 1 : 1; + return counts; + }, {}); + const duplicateIds = Object.keys(idCounts).filter(id => idCounts[id] > 1); + Assert.deepEqual(duplicateIds, [], "All IDs should only be used once"); +}); diff --git a/comm/mail/components/unifiedtoolbar/test/unit/test_customizationState.js b/comm/mail/components/unifiedtoolbar/test/unit/test_customizationState.js new file mode 100644 index 0000000000..048b5c5cde --- /dev/null +++ b/comm/mail/components/unifiedtoolbar/test/unit/test_customizationState.js @@ -0,0 +1,64 @@ +/* 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/. */ + +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +const { storeState, getState } = ChromeUtils.importESModule( + "resource:///modules/CustomizationState.mjs" +); + +add_setup(function () { + // Ensure xulStore has a profile to refer to. + do_get_profile(); +}); + +add_task(function test_getState_empty() { + const state = getState(); + Assert.equal(typeof state, "object", "State should be an object"); + Assert.deepEqual(state, {}, "Empty state should be an empty object"); +}); + +add_task(async function test_storeState_observer() { + const stateChangeObserved = TestUtils.topicObserved( + "unified-toolbar-state-change" + ); + storeState({ + mail: ["write-message", "spacer", "search-bar", "spacer"], + }); + await stateChangeObserved; +}); + +add_task(function test_storeState_getState() { + const state = { + mail: ["write-message", "spacer", "search-bar", "spacer"], + calendar: [], + }; + const previousState = getState(); + Assert.notDeepEqual( + previousState, + state, + "Current state should be different from the state to write" + ); + storeState(state); + const newState = getState(); + Assert.deepEqual( + newState, + state, + "State loaded should matche the stored state" + ); + Assert.notStrictEqual( + newState, + state, + "State loaded should not be the same object as what was saved" + ); +}); + +registerCleanupFunction(() => { + Services.xulStore.removeValue( + "chrome://messenger/content/messenger.xhtml", + "unifiedToolbar", + "state" + ); +}); diff --git a/comm/mail/components/unifiedtoolbar/test/unit/test_toolbarMigration.js b/comm/mail/components/unifiedtoolbar/test/unit/test_toolbarMigration.js new file mode 100644 index 0000000000..637d40e066 --- /dev/null +++ b/comm/mail/components/unifiedtoolbar/test/unit/test_toolbarMigration.js @@ -0,0 +1,431 @@ +/* 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, clearXULToolbarState } = + ChromeUtils.importESModule("resource:///modules/ToolbarMigration.sys.mjs"); +const { getState, storeState } = ChromeUtils.importESModule( + "resource:///modules/CustomizationState.mjs" +); +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const MESSENGER_WINDOW = "chrome://messenger/content/messenger.xhtml"; + +function setXULToolbarState( + currentSet = "", + defaultSet = "", + toolbarId = "mail-bar3" +) { + Services.xulStore.setValue( + MESSENGER_WINDOW, + toolbarId, + "currentset", + currentSet + ); + Services.xulStore.setValue( + MESSENGER_WINDOW, + toolbarId, + "defaultset", + defaultSet + ); +} + +add_setup(() => { + do_get_profile(); + storeState({}); +}); + +add_task(function test_migration_customized() { + setXULToolbarState( + "button-getmsg,button-newmsg,button-reply,spacer,qfb-show-filter-bar,button-file,folder-location-container,spring,gloda-search,button-appmenu" + ); + setXULToolbarState( + "menubar-items,spring,button-addons", + "", + "toolbar-menubar" + ); + setXULToolbarState("button-delete", "", "tabbar-toolbar"); + + migrateToolbarForSpace("mail"); + + const newState = getState(); + + Assert.deepEqual( + newState.mail, + [ + "get-messages", + "write-message", + "reply", + "spacer", + "quick-filter-bar", + "move-to", + "folder-location", + "spacer", + "search-bar", + "spacer", + "add-ons-and-themes", + "delete", + ], + "Items were combined and migrated" + ); + 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" + ); + + storeState({}); +}); + +add_task(function test_migration_defaults() { + setXULToolbarState(); + setXULToolbarState("", "", "toolbar-menubar"); + setXULToolbarState("", "", "tabbar-toolbar"); + + migrateToolbarForSpace("mail"); + + const newState = getState(); + + Assert.ok(!newState.mail, "New default state was preserved"); + 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" + ); + + storeState({}); +}); + +add_task(function test_migration_empty() { + setXULToolbarState("__empty"); + setXULToolbarState("__empty", "menubar-items,spring", "toolbar-menubar"); + setXULToolbarState("__empty", "", "tabbar-toolbar"); + + migrateToolbarForSpace("mail"); + + const newState = getState(); + + Assert.deepEqual(newState.mail, [], "The toolbar contents were emptied"); + 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" + ); + + storeState({}); +}); + +add_task(function test_migration_noop() { + const state = { mail: ["spacer", "search-bar", "spacer"] }; + storeState(state); + + migrateToolbarForSpace("mail"); + + const newState = getState(); + + Assert.deepEqual(newState, state, "Customization state is not modified"); + 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" + ); + + storeState({}); +}); + +add_task(function test_calendar_migration() { + setXULToolbarState( + "calendar-synchronize-button,calendar-newevent-button,separator,calendar-edit-button,calendar-delete-button,spring,calendar-unifinder-button,calendar-appmenu-button", + "", + "calendar-toolbar2" + ); + setXULToolbarState( + "menubar-items,spring,button-addons", + "", + "toolbar-menubar" + ); + setXULToolbarState("button-delete", "", "tabbar-toolbar"); + + migrateToolbarForSpace("calendar"); + + const newState = getState(); + + Assert.deepEqual( + newState.calendar, + [ + "synchronize", + "new-event", + "edit-event", + "delete-event", + "spacer", + "unifinder", + "spacer", + "add-ons-and-themes", + ], + "Items were combined and migrated" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "calendar-toolbar2", + "currentset" + ), + "Old toolbar state is cleared" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "calendar-toolbar2", + "defaultset" + ), + "Old toolbar default state is cleared" + ); + + storeState({}); +}); + +add_task(function test_calendar_migration_defaults() { + setXULToolbarState("", "", "calendar-toolbar2"); + setXULToolbarState("", "", "toolbar-menubar"); + setXULToolbarState("", "", "tabbar-toolbar"); + + migrateToolbarForSpace("calendar"); + + const newState = getState(); + + Assert.deepEqual( + newState.calendar, + [ + "synchronize", + "new-event", + "new-task", + "edit-event", + "delete-event", + "spacer", + ], + "Default states were combined and migrated" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "calendar-toolbar2", + "currentset" + ), + "Old toolbar state is cleared" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "calendar-toolbar2", + "defaultset" + ), + "Old toolbar default state is cleared" + ); + + storeState({}); +}); + +add_task(function test_tasks_migration() { + setXULToolbarState( + "task-synchronize-button,task-newtask-button,task-edit-button,task-delete-button,task-print-button,spring,task-appmenu-button", + "", + "task-toolbar2" + ); + setXULToolbarState( + "menubar-items,spring,button-addons", + "", + "toolbar-menubar" + ); + setXULToolbarState("button-delete", "", "tabbar-toolbar"); + + migrateToolbarForSpace("tasks"); + + const newState = getState(); + + Assert.deepEqual( + newState.tasks, + [ + "synchronize", + "new-task", + "edit-event", + "delete-event", + "print-event", + "spacer", + "add-ons-and-themes", + ], + "Items were combined and migrated" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "task-toolbar2", + "currentset" + ), + "Old toolbar state is cleared" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "task-toolbar2", + "defaultset" + ), + "Old toolbar default state is cleared" + ); + + storeState({}); +}); + +add_task(function test_tasks_migration_defaults() { + setXULToolbarState("", "", "task-toolbar2"); + setXULToolbarState("", "", "toolbar-menubar"); + setXULToolbarState("", "", "tabbar-toolbar"); + + migrateToolbarForSpace("tasks"); + + const newState = getState(); + + Assert.deepEqual( + newState.tasks, + [ + "synchronize", + "new-event", + "new-task", + "edit-event", + "delete-event", + "spacer", + ], + "Default states were combined and migrated" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "task-toolbar2", + "currentset" + ), + "Old toolbar state is cleared" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "task-toolbar2", + "defaultset" + ), + "Old toolbar default state is cleared" + ); + + storeState({}); +}); + +add_task(function test_global_items_migration() { + setXULToolbarState( + "menubar-items,spring,button-addons", + "", + "toolbar-menubar" + ); + setXULToolbarState("button-delete", "", "tabbar-toolbar"); + + migrateToolbarForSpace("settings"); + + const newState = getState(); + + Assert.deepEqual(newState.settings, [ + "spacer", + "search-bar", + "spacer", + "add-ons-and-themes", + ]); + + storeState({}); +}); + +add_task(function test_global_items_migration_defaults() { + setXULToolbarState("", "", "toolbar-menubar"); + setXULToolbarState("", "", "tabbar-toolbar"); + + migrateToolbarForSpace("settings"); + + const newState = getState(); + + Assert.deepEqual(newState.settings, ["spacer", "search-bar", "spacer"]); + + storeState({}); +}); + +add_task(function test_clear_xul_toolbar_state() { + setXULToolbarState( + "menubar-items,spring,button-addons", + "menubar-items,spring", + "toolbar-menubar" + ); + + clearXULToolbarState("toolbar-menubar"); + + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "toolbar-menubar", + "currentset" + ), + "Old toolbar state is cleared" + ); + Assert.ok( + !Services.xulStore.hasValue( + MESSENGER_WINDOW, + "toolbar-menubar", + "defaultset" + ), + "Old toolbar default state is cleared" + ); +}); + +add_task(function test_migration_defaults_with_extension() { + setXULToolbarState( + AppConstants.platform == "macosx" + ? "button-getmsg,button-newmsg,button-tag,qfb-show-filter-bar,spring,gloda-search,extension1,extension2,button-appmenu" + : "button-getmsg,button-newmsg,separator,button-tag,qfb-show-filter-bar,spring,gloda-search,extension1,extension2,button-appmenu" + ); + setXULToolbarState("", "", "toolbar-menubar"); + setXULToolbarState("", "", "tabbar-toolbar"); + Services.xulStore.setValue( + MESSENGER_WINDOW, + "mail-bar3", + "extensionset", + "extension1,extension2" + ); + + migrateToolbarForSpace("mail"); + + const newState = getState(); + + Assert.ok(!newState.mail, "New default state was preserved"); + 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" + ); + + storeState({}); +}); diff --git a/comm/mail/components/unifiedtoolbar/test/unit/xpcshell.ini b/comm/mail/components/unifiedtoolbar/test/unit/xpcshell.ini new file mode 100644 index 0000000000..ec9807b399 --- /dev/null +++ b/comm/mail/components/unifiedtoolbar/test/unit/xpcshell.ini @@ -0,0 +1,8 @@ +[DEFAULT] +head = + +[test_buttonStyle.js] +[test_customizableItems.js] +[test_customizableItemsDetails.js] +[test_customizationState.js] +[test_toolbarMigration.js] |