diff options
Diffstat (limited to 'comm/mail/components/preferences/test/browser')
12 files changed, 2598 insertions, 0 deletions
diff --git a/comm/mail/components/preferences/test/browser/browser.ini b/comm/mail/components/preferences/test/browser/browser.ini new file mode 100644 index 0000000000..44b7d97a31 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser.ini @@ -0,0 +1,20 @@ +[DEFAULT] +head = head.js +prefs = + mail.provider.suppress_dialog_on_startup=true + mail.spotlight.firstRunDone=true + mail.winsearch.firstRunDone=true + mailnews.start_page.override_url=about:blank + mailnews.start_page.url=about:blank +subsuite = thunderbird + +[browser_chat.js] +[browser_cloudfile.js] +support-files = files/icon.svg files/management.html +[browser_compose.js] +[browser_general.js] +[browser_openPreferences.js] +[browser_privacy.js] +[browser_sync.js] +skip-if = !nightly_build +support-files = files/avatar.png diff --git a/comm/mail/components/preferences/test/browser/browser_chat.js b/comm/mail/components/preferences/test/browser/browser_chat.js new file mode 100644 index 0000000000..009f2a9211 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser_chat.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async () => { + await testCheckboxes( + "paneChat", + "chatPaneCategory", + { + checkboxID: "reportIdle", + pref: "messenger.status.reportIdle", + enabledElements: ["#autoAway", "#timeBeforeAway"], + }, + { + checkboxID: "sendTyping", + pref: "purple.conversations.im.send_typing", + }, + { + checkboxID: "desktopChatNotifications", + pref: "mail.chat.show_desktop_notifications", + }, + { + checkboxID: "getAttention", + pref: "messenger.options.getAttentionOnNewMessages", + }, + { + checkboxID: "chatNotification", + pref: "mail.chat.play_sound", + enabledElements: ["#chatSoundType radio"], + } + ); + + Services.prefs.setBoolPref("messenger.status.reportIdle", true); + await testCheckboxes("paneChat", "chatPaneCategory", { + checkboxID: "autoAway", + pref: "messenger.status.awayWhenIdle", + enabledElements: ["#defaultIdleAwayMessage"], + }); + + Services.prefs.setBoolPref("mail.chat.play_sound", true); + await testRadioButtons("paneChat", "chatPaneCategory", { + pref: "mail.chat.play_sound.type", + states: [ + { + id: "chatSoundSystemSound", + prefValue: 0, + }, + { + id: "chatSoundCustom", + prefValue: 1, + enabledElements: ["#chatSoundUrlLocation", "#browseForChatSound"], + }, + ], + }); +}); + +add_task(async function testMessageStylePreview() { + await openNewPrefsTab("paneChat", "chatPaneCategory"); + const conversationLoad = TestUtils.topicObserved("conversation-loaded"); + const [subject] = await conversationLoad; + do { + await BrowserTestUtils.waitForEvent(subject, "MessagesDisplayed"); + } while (subject.getPendingMessagesCount() > 0); + const messageParent = subject.contentChatNode; + let message = messageParent.firstElementChild; + const messages = new Set(); + while (message) { + ok(message._originalMsg); + messages.add(message._originalMsg); + message = message.nextElementSibling; + } + is(messages.size, 3, "All 3 messages displayed"); + await closePrefsTab(); +}); diff --git a/comm/mail/components/preferences/test/browser/browser_cloudfile.js b/comm/mail/components/preferences/test/browser/browser_cloudfile.js new file mode 100644 index 0000000000..9f395f9ab8 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser_cloudfile.js @@ -0,0 +1,796 @@ +/* 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/. */ + +/* eslint-env webextensions */ + +let { cloudFileAccounts } = ChromeUtils.import( + "resource:///modules/cloudFileAccounts.jsm" +); +let { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" +); + +function ManagementScript() { + browser.test.onMessage.addListener((message, assertMessage, browserStyle) => { + if (message !== "check-style") { + return; + } + function verifyButton(buttonElement, expected) { + let buttonStyle = window.getComputedStyle(buttonElement); + let buttonBackgroundColor = buttonStyle.backgroundColor; + if (browserStyle && expected.hasBrowserStyleClass) { + browser.test.assertEq( + "rgb(9, 150, 248)", + buttonBackgroundColor, + assertMessage + ); + } else { + browser.test.assertTrue( + buttonBackgroundColor !== "rgb(9, 150, 248)", + assertMessage + ); + } + } + + function verifyCheckboxOrRadio(element, expected) { + let style = window.getComputedStyle(element); + let styledBackground = element.checked + ? "rgb(9, 150, 248)" + : "rgb(255, 255, 255)"; + if (browserStyle && expected.hasBrowserStyleClass) { + browser.test.assertEq( + styledBackground, + style.backgroundColor, + assertMessage + ); + } else { + browser.test.assertTrue( + style.backgroundColor != styledBackground, + assertMessage + ); + } + } + + let normalButton = document.getElementById("normalButton"); + let browserStyleButton = document.getElementById("browserStyleButton"); + verifyButton(normalButton, { hasBrowserStyleClass: false }); + verifyButton(browserStyleButton, { hasBrowserStyleClass: true }); + + let normalCheckbox1 = document.getElementById("normalCheckbox1"); + let normalCheckbox2 = document.getElementById("normalCheckbox2"); + let browserStyleCheckbox = document.getElementById("browserStyleCheckbox"); + verifyCheckboxOrRadio(normalCheckbox1, { hasBrowserStyleClass: false }); + verifyCheckboxOrRadio(normalCheckbox2, { hasBrowserStyleClass: false }); + verifyCheckboxOrRadio(browserStyleCheckbox, { + hasBrowserStyleClass: true, + }); + + let normalRadio1 = document.getElementById("normalRadio1"); + let normalRadio2 = document.getElementById("normalRadio2"); + let browserStyleRadio = document.getElementById("browserStyleRadio"); + verifyCheckboxOrRadio(normalRadio1, { hasBrowserStyleClass: false }); + verifyCheckboxOrRadio(normalRadio2, { hasBrowserStyleClass: false }); + verifyCheckboxOrRadio(browserStyleRadio, { hasBrowserStyleClass: true }); + + browser.test.notifyPass("management-ui-browser_style"); + }); + browser.test.sendMessage("management-ui-ready"); +} + +let extension; +async function startExtension(browser_style) { + let cloud_file = { + name: "Mochitest", + management_url: "management.html", + }; + + switch (browser_style) { + case "true": + cloud_file.browser_style = true; + break; + case "false": + cloud_file.browser_style = false; + break; + } + + extension = ExtensionTestUtils.loadExtension({ + async background() { + browser.test.onMessage.addListener(async message => { + if (message != "set-configured") { + return; + } + let accounts = await browser.cloudFile.getAllAccounts(); + for (let account of accounts) { + await browser.cloudFile.updateAccount(account.id, { + configured: true, + }); + } + browser.test.sendMessage("ready"); + }); + }, + files: { + "management.html": `<html> + <body> + <a id="a" href="https://www.example.com/">Click me!</a> + <button id="normalButton" name="button" class="default">Default</button> + <button id="browserStyleButton" name="button" class="browser-style default">Default</button> + + <input id="normalCheckbox1" type="checkbox"/> + <input id="normalCheckbox2" type="checkbox"/><label>Checkbox</label> + <div class="browser-style"> + <input id="browserStyleCheckbox" type="checkbox"><label for="browserStyleCheckbox">Checkbox</label> + </div> + + <input id="normalRadio1" type="radio"/> + <input id="normalRadio2" type="radio"/><label>Radio</label> + <div class="browser-style"> + <input id="browserStyleRadio" checked="" type="radio"><label for="browserStyleRadio">Radio</label> + </div> + </body> + <script src="management.js" type="text/javascript"></script> + </html>`, + "management.js": ManagementScript, + }, + manifest: { + cloud_file, + applications: { gecko: { id: "cloudfile@mochitest" } }, + }, + }); + + info("Starting extension"); + await extension.startup(); + + if (accountIsConfigured) { + extension.sendMessage("set-configured"); + await extension.awaitMessage("ready"); + } +} + +add_task(async () => { + // Register a fake provider representing a built-in provider. We don't + // currently ship any built-in providers, but if we did, we should check + // if they are present before doing this. Built-in providers can be + // problematic for artifact builds. + cloudFileAccounts.registerProvider("Fake-Test", { + displayName: "XYZ Fake", + type: "ext-fake@extensions.thunderbird.net", + }); + registerCleanupFunction(() => { + cloudFileAccounts.unregisterProvider("Fake-Test"); + }); +}); + +let accountIsConfigured = false; + +// Mock the prompt service. We're going to be asked if we're sure +// we want to remove an account, so let's say yes. + +/** @implements {nsIPromptService} */ +let mockPromptService = { + confirmCount: 0, + confirm() { + this.confirmCount++; + return true; + }, + QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]), +}; +/** @implements {nsIExternalProtocolService} */ +let mockExternalProtocolService = { + _loadedURLs: [], + externalProtocolHandlerExists(aProtocolScheme) {}, + getApplicationDescription(aScheme) {}, + getProtocolHandlerInfo(aProtocolScheme) {}, + getProtocolHandlerInfoFromOS(aProtocolScheme, aFound) {}, + isExposedProtocol(aProtocolScheme) {}, + loadURI(aURI, aWindowContext) { + this._loadedURLs.push(aURI.spec); + }, + setProtocolHandlerDefaults(aHandlerInfo, aOSHandlerExists) {}, + urlLoaded(aURL) { + return this._loadedURLs.includes(aURL); + }, + QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]), +}; + +let originalPromptService = Services.prompt; +Services.prompt = mockPromptService; + +let mockExternalProtocolServiceCID = MockRegistrar.register( + "@mozilla.org/uriloader/external-protocol-service;1", + mockExternalProtocolService +); + +registerCleanupFunction(() => { + Services.prompt = originalPromptService; + MockRegistrar.unregister(mockExternalProtocolServiceCID); +}); + +add_task(async function addRemoveAccounts() { + is(cloudFileAccounts.providers.length, 1); + is(cloudFileAccounts.accounts.length, 0); + + // Load the preferences tab. + + let { prefsDocument, prefsWindow } = await openNewPrefsTab( + "paneCompose", + "compositionAttachmentsCategory" + ); + + // Check everything is as it should be. + + let accountList = prefsDocument.getElementById("cloudFileView"); + is(accountList.itemCount, 0); + + let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons"); + ok(!buttonList.hidden); + is(buttonList.childElementCount, 1); + is( + buttonList.children[0].getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + + let menuButton = prefsDocument.getElementById("addCloudFileAccount"); + ok(menuButton.hidden); + is(menuButton.itemCount, 1); + is( + menuButton.getItemAtIndex(0).getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + + let removeButton = prefsDocument.getElementById("removeCloudFileAccount"); + ok(removeButton.disabled); + + let cloudFileDefaultPanel = prefsDocument.getElementById( + "cloudFileDefaultPanel" + ); + ok(!cloudFileDefaultPanel.hidden); + + let browserWrapper = prefsDocument.getElementById("cloudFileSettingsWrapper"); + is(browserWrapper.childElementCount, 0); + + // Register our test provider. + + await startExtension(); + is(cloudFileAccounts.providers.length, 2); + is(cloudFileAccounts.accounts.length, 0); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is(buttonList.childElementCount, 2); + is( + buttonList.children[0].getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest"); + is( + buttonList.children[1].style.listStyleImage, + `url("chrome://messenger/content/extension.svg")` + ); + + is(menuButton.itemCount, 2); + is( + menuButton.getItemAtIndex(0).getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + is( + menuButton.getItemAtIndex(1).getAttribute("value"), + "ext-cloudfile@mochitest" + ); + is( + menuButton.getItemAtIndex(1).getAttribute("image"), + "chrome://messenger/content/extension.svg" + ); + + // Create a new account. + + EventUtils.synthesizeMouseAtCenter( + buttonList.children[1], + { clickCount: 1 }, + prefsWindow + ); + is(cloudFileAccounts.accounts.length, 1); + is(cloudFileAccounts.configuredAccounts.length, 0); + + let account = cloudFileAccounts.accounts[0]; + let accountKey = account.accountKey; + is(cloudFileAccounts.accounts[0].type, "ext-cloudfile@mochitest"); + + // Check prefs were updated. + + is( + Services.prefs.getCharPref( + `mail.cloud_files.accounts.${accountKey}.displayName` + ), + "Mochitest" + ); + is( + Services.prefs.getCharPref(`mail.cloud_files.accounts.${accountKey}.type`), + "ext-cloudfile@mochitest" + ); + + // Check UI was updated. + + is(accountList.itemCount, 1); + is(accountList.selectedIndex, 0); + ok(!removeButton.disabled); + + let accountListItem = accountList.selectedItem; + is(accountListItem.getAttribute("value"), accountKey); + is( + accountListItem.querySelector(".typeIcon:not(.configuredWarning)").src, + "chrome://messenger/content/extension.svg" + ); + is(accountListItem.querySelector("label").value, "Mochitest"); + is(accountListItem.querySelector(".configuredWarning").hidden, false); + + ok(cloudFileDefaultPanel.hidden); + is(browserWrapper.childElementCount, 1); + + let browser = browserWrapper.firstElementChild; + if ( + browser.webProgress?.isLoadingDocument || + browser.currentURI?.spec == "about:blank" + ) { + await BrowserTestUtils.browserLoaded(browser); + } + is( + browser.currentURI.pathQueryRef, + `/management.html?accountId=${accountKey}` + ); + await extension.awaitMessage("management-ui-ready"); + + let tabmail = document.getElementById("tabmail"); + let tabCount = tabmail.tabInfo.length; + BrowserTestUtils.synthesizeMouseAtCenter("a", {}, browser); + // It might take a moment to get to the external protocol service. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 500)); + ok( + mockExternalProtocolService.urlLoaded("https://www.example.com/"), + "Link click sent to external protocol service." + ); + is(tabmail.tabInfo.length, tabCount, "No new tab opened"); + + // Rename the account. + + EventUtils.synthesizeMouseAtCenter( + accountListItem, + { clickCount: 1 }, + prefsWindow + ); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is( + prefsDocument.activeElement.closest("input"), + accountListItem.querySelector("input") + ); + ok(accountListItem.querySelector("label").hidden); + ok(!accountListItem.querySelector("input").hidden); + is(accountListItem.querySelector("input").value, "Mochitest"); + EventUtils.synthesizeKey("VK_RIGHT", undefined, prefsWindow); + EventUtils.synthesizeKey("!", undefined, prefsWindow); + EventUtils.synthesizeKey("VK_RETURN", undefined, prefsWindow); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is(prefsDocument.activeElement, accountList); + ok(!accountListItem.querySelector("label").hidden); + is(accountListItem.querySelector("label").value, "Mochitest!"); + ok(accountListItem.querySelector("input").hidden); + is( + Services.prefs.getCharPref( + `mail.cloud_files.accounts.${accountKey}.displayName` + ), + "Mochitest!" + ); + + // Start to rename the account, but bail out. + + EventUtils.synthesizeMouseAtCenter( + accountListItem, + { clickCount: 1 }, + prefsWindow + ); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is( + prefsDocument.activeElement.closest("input"), + accountListItem.querySelector("input") + ); + EventUtils.synthesizeKey("O", undefined, prefsWindow); + EventUtils.synthesizeKey("o", undefined, prefsWindow); + EventUtils.synthesizeKey("p", undefined, prefsWindow); + EventUtils.synthesizeKey("s", undefined, prefsWindow); + EventUtils.synthesizeKey("VK_ESCAPE", undefined, prefsWindow); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is(prefsDocument.activeElement, accountList); + ok(!accountListItem.querySelector("label").hidden); + is(accountListItem.querySelector("label").value, "Mochitest!"); + ok(accountListItem.querySelector("input").hidden); + is( + Services.prefs.getCharPref( + `mail.cloud_files.accounts.${accountKey}.displayName` + ), + "Mochitest!" + ); + + // Configure the account. + + account.configured = true; + accountIsConfigured = true; + cloudFileAccounts.emit("accountConfigured", account); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is(accountListItem.querySelector(".configuredWarning").hidden, true); + is(cloudFileAccounts.accounts.length, 1); + is(cloudFileAccounts.configuredAccounts.length, 1); + + // Remove the test provider. The list item, button, and browser should disappear. + + info("Stopping extension"); + await extension.unload(); + is(cloudFileAccounts.providers.length, 1); + is(cloudFileAccounts.accounts.length, 0); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is(buttonList.childElementCount, 1); + is( + buttonList.children[0].getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + is(menuButton.itemCount, 1); + is( + menuButton.getItemAtIndex(0).getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + is(accountList.itemCount, 0); + ok(!cloudFileDefaultPanel.hidden); + is(browserWrapper.childElementCount, 0); + + // Re-add the test provider. + + await startExtension(); + + is(cloudFileAccounts.providers.length, 2); + is(cloudFileAccounts.accounts.length, 1); + is(cloudFileAccounts.configuredAccounts.length, 1); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is(buttonList.childElementCount, 2); + is( + buttonList.children[0].getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest"); + + is(menuButton.itemCount, 2); + is( + menuButton.getItemAtIndex(0).getAttribute("value"), + "ext-fake@extensions.thunderbird.net" + ); + is( + menuButton.getItemAtIndex(1).getAttribute("value"), + "ext-cloudfile@mochitest" + ); + + is(accountList.itemCount, 1); + is(accountList.selectedIndex, -1); + ok(removeButton.disabled); + + accountListItem = accountList.getItemAtIndex(0); + is( + Services.prefs.getCharPref( + `mail.cloud_files.accounts.${accountKey}.displayName` + ), + "Mochitest!" + ); + + EventUtils.synthesizeMouseAtCenter( + accountList.getItemAtIndex(0), + { clickCount: 1 }, + prefsWindow + ); + ok(!removeButton.disabled); + EventUtils.synthesizeMouseAtCenter( + removeButton, + { clickCount: 1 }, + prefsWindow + ); + is(mockPromptService.confirmCount, 1); + + ok( + !Services.prefs.prefHasUserValue( + `mail.cloud_files.accounts.${accountKey}.displayName` + ) + ); + ok( + !Services.prefs.prefHasUserValue( + `mail.cloud_files.accounts.${accountKey}.type` + ) + ); + + is(cloudFileAccounts.providers.length, 2); + is(cloudFileAccounts.accounts.length, 0); + + info("Stopping extension"); + await extension.unload(); + is(cloudFileAccounts.providers.length, 1); + is(cloudFileAccounts.accounts.length, 0); + + // Close the preferences tab. + + await closePrefsTab(); +}); + +async function subtestBrowserStyle(assertMessage, expected) { + is(cloudFileAccounts.providers.length, 1); + is(cloudFileAccounts.accounts.length, 0); + + // Load the preferences tab. + + let { prefsDocument, prefsWindow } = await openNewPrefsTab( + "paneCompose", + "compositionAttachmentsCategory" + ); + + // Minimal check everything is as it should be. + + let accountList = prefsDocument.getElementById("cloudFileView"); + is(accountList.itemCount, 0); + + let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons"); + ok(!buttonList.hidden); + + let browserWrapper = prefsDocument.getElementById("cloudFileSettingsWrapper"); + is(browserWrapper.childElementCount, 0); + + // Register our test provider. + + await startExtension(expected.browser_style); + is(cloudFileAccounts.providers.length, 2); + is(cloudFileAccounts.accounts.length, 0); + + await new Promise(resolve => prefsWindow.requestAnimationFrame(resolve)); + + is(buttonList.childElementCount, 2); + is(buttonList.children[1].getAttribute("value"), "ext-cloudfile@mochitest"); + + // Create a new account. + + EventUtils.synthesizeMouseAtCenter( + buttonList.children[1], + { clickCount: 1 }, + prefsWindow + ); + is(cloudFileAccounts.accounts.length, 1); + is(cloudFileAccounts.configuredAccounts.length, 0); + + let account = cloudFileAccounts.accounts[0]; + let accountKey = account.accountKey; + is(cloudFileAccounts.accounts[0].type, "ext-cloudfile@mochitest"); + + // Minimal check UI was updated. + + is(accountList.itemCount, 1); + is(accountList.selectedIndex, 0); + + let accountListItem = accountList.selectedItem; + is(accountListItem.getAttribute("value"), accountKey); + + is(browserWrapper.childElementCount, 1); + let browser = browserWrapper.firstElementChild; + if ( + browser.webProgress?.isLoadingDocument || + browser.currentURI?.spec == "about:blank" + ) { + await BrowserTestUtils.browserLoaded(browser); + } + is( + browser.currentURI.pathQueryRef, + `/management.html?accountId=${accountKey}` + ); + await extension.awaitMessage("management-ui-ready"); + + // Test browser_style + + extension.sendMessage( + "check-style", + assertMessage, + expected.browser_style == "true" + ); + await extension.awaitFinish("management-ui-browser_style"); + + // Remove the account + + accountListItem = accountList.getItemAtIndex(0); + EventUtils.synthesizeMouseAtCenter( + accountList.getItemAtIndex(0), + { clickCount: 1 }, + prefsWindow + ); + + let removeButton = prefsDocument.getElementById("removeCloudFileAccount"); + ok(!removeButton.disabled); + EventUtils.synthesizeMouseAtCenter( + removeButton, + { clickCount: 1 }, + prefsWindow + ); + is(mockPromptService.confirmCount, expected.confirmCount); + + is(cloudFileAccounts.providers.length, 2); + is(cloudFileAccounts.accounts.length, 0); + + info("Stopping extension"); + await extension.unload(); + is(cloudFileAccounts.providers.length, 1); + is(cloudFileAccounts.accounts.length, 0); + + // Close the preferences tab. + + await closePrefsTab(); +} + +add_task(async function test_without_setting_browser_style() { + await subtestBrowserStyle( + "Expected correct style when browser_style is excluded", + { + confirmCount: 2, + browser_style: "default", + } + ); +}); + +add_task(async function test_with_browser_style_set_to_true() { + await subtestBrowserStyle( + "Expected correct style when browser_style is set to `true`", + { + confirmCount: 3, + browser_style: "true", + } + ); +}); + +add_task(async function test_with_browser_style_set_to_false() { + await subtestBrowserStyle( + "Expected no style when browser_style is set to `false`", + { + confirmCount: 4, + browser_style: "false", + } + ); +}); + +add_task(async function accountListOverflow() { + is(cloudFileAccounts.providers.length, 1); + is(cloudFileAccounts.accounts.length, 0); + + // Register our test provider. + + await startExtension(); + + is(cloudFileAccounts.providers.length, 2); + is(cloudFileAccounts.accounts.length, 0); + + // Load the preferences tab. + + let { prefsDocument, prefsWindow } = await openNewPrefsTab( + "paneCompose", + "compositionAttachmentsCategory" + ); + + let accountList = prefsDocument.getElementById("cloudFileView"); + is(accountList.itemCount, 0); + + let buttonList = prefsDocument.getElementById("addCloudFileAccountButtons"); + ok(!buttonList.hidden); + is(buttonList.childElementCount, 2); + is(buttonList.children[0].getAttribute("value"), "ext-cloudfile@mochitest"); + + let menuButton = prefsDocument.getElementById("addCloudFileAccount"); + ok(menuButton.hidden); + + // Add new accounts until the list overflows. The list of buttons should be hidden + // and the button with the drop-down should appear. + + let count = 0; + do { + let readyPromise = extension.awaitMessage("management-ui-ready"); + EventUtils.synthesizeMouseAtCenter( + buttonList.children[0], + { clickCount: 1 }, + prefsWindow + ); + await readyPromise; + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(r => setTimeout(r, 500)); + if (buttonList.hidden) { + break; + } + } while (++count < 25); + + ok(count < 24); // If count reaches 25, we have a problem. + ok(!menuButton.hidden); + + // Remove the added accounts. The list of buttons should not reappear and the + // button with the drop-down should remain. + + let removeButton = prefsDocument.getElementById("removeCloudFileAccount"); + do { + EventUtils.synthesizeMouseAtCenter( + accountList.getItemAtIndex(0), + { clickCount: 1 }, + prefsWindow + ); + EventUtils.synthesizeMouseAtCenter( + removeButton, + { clickCount: 1 }, + prefsWindow + ); + await new Promise(resolve => setTimeout(resolve)); + } while (--count > 0); + + ok(buttonList.hidden); + ok(!menuButton.hidden); + + // Close the preferences tab. + + await closePrefsTab(); + info("Stopping extension"); + await extension.unload(); + Services.prefs.deleteBranch("mail.cloud_files.accounts"); +}); + +add_task(async function accountListOrder() { + is(cloudFileAccounts.providers.length, 1); + is(cloudFileAccounts.accounts.length, 0); + + for (let [key, displayName] of [ + ["someKey1", "carl's Account"], + ["someKey2", "Amber's Account"], + ["someKey3", "alice's Account"], + ["someKey4", "Bob's Account"], + ]) { + Services.prefs.setCharPref( + `mail.cloud_files.accounts.${key}.type`, + "ext-cloudfile@mochitest" + ); + Services.prefs.setCharPref( + `mail.cloud_files.accounts.${key}.displayName`, + displayName + ); + } + + // Register our test provider. + + await startExtension(); + + is(cloudFileAccounts.providers.length, 2); + is(cloudFileAccounts.accounts.length, 4); + + let { prefsDocument } = await openNewPrefsTab( + "paneCompose", + "compositionAttachmentsCategory" + ); + + let accountList = prefsDocument.getElementById("cloudFileView"); + is(accountList.itemCount, 4); + + is(accountList.getItemAtIndex(0).value, "someKey3"); + is(accountList.getItemAtIndex(1).value, "someKey2"); + is(accountList.getItemAtIndex(2).value, "someKey4"); + is(accountList.getItemAtIndex(3).value, "someKey1"); + + await closePrefsTab(); + info("Stopping extension"); + await extension.unload(); + Services.prefs.deleteBranch("mail.cloud_files.accounts"); +}); diff --git a/comm/mail/components/preferences/test/browser/browser_compose.js b/comm/mail/components/preferences/test/browser/browser_compose.js new file mode 100644 index 0000000000..ea253cb555 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser_compose.js @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async () => { + await testCheckboxes( + "paneCompose", + "compositionMainCategory", + { + checkboxID: "addExtension", + pref: "mail.forward_add_extension", + }, + { + checkboxID: "autoSave", + pref: "mail.compose.autosave", + enabledElements: ["#autoSaveInterval"], + }, + { + checkboxID: "mailWarnOnSendAccelKey", + pref: "mail.warn_on_send_accel_key", + }, + { + checkboxID: "spellCheckBeforeSend", + pref: "mail.SpellCheckBeforeSend", + }, + { + checkboxID: "inlineSpellCheck", + pref: "mail.spellcheck.inline", + } + ); + + await testCheckboxes( + "paneCompose", + "FontSelect", + { + checkboxID: "useReaderDefaults", + pref: "msgcompose.default_colors", + enabledInverted: true, + enabledElements: [ + "#textColorLabel", + "#textColorButton", + "#backgroundColorLabel", + "#backgroundColorButton", + ], + }, + { + checkboxID: "defaultToParagraph", + pref: "mail.compose.default_to_paragraph", + } + ); + + await testCheckboxes( + "paneCompose", + "compositionAddressingCategory", + { + checkboxID: "addressingAutocomplete", + pref: "mail.enable_autocomplete", + }, + { + checkboxID: "autocompleteLDAP", + pref: "ldap_2.autoComplete.useDirectory", + enabledElements: ["#directoriesList", "#editButton"], + }, + { + checkboxID: "emailCollectionOutgoing", + pref: "mail.collect_email_address_outgoing", + enabledElements: ["#localDirectoriesList"], + } + ); +}); + +add_task(async () => { + await testCheckboxes( + "paneCompose", + "compositionAttachmentsCategory", + { + checkboxID: "attachment_reminder_label", + pref: "mail.compose.attachment_reminder", + enabledElements: ["#attachment_reminder_button"], + }, + { + checkboxID: "enableThreshold", + pref: "mail.compose.big_attachments.notify", + enabledElements: ["#cloudFileThreshold"], + } + ); +}); diff --git a/comm/mail/components/preferences/test/browser/browser_general.js b/comm/mail/components/preferences/test/browser/browser_general.js new file mode 100644 index 0000000000..f011be1e38 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser_general.js @@ -0,0 +1,380 @@ +/* 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/. */ + +var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" +); + +add_task(async () => { + requestLongerTimeout(2); + + // Temporarily disable `Once` StaticPrefs check for this test so that we + // can change layers.acceleration.disabled without debug builds failing. + await SpecialPowers.pushPrefEnv({ + set: [["preferences.force-disable.check.once.policy", true]], + }); +}); + +add_task(async () => { + await testCheckboxes( + "paneGeneral", + "generalCategory", + { + checkboxID: "mailnewsStartPageEnabled", + pref: "mailnews.start_page.enabled", + enabledElements: [ + "#mailnewsStartPageUrl", + "#mailnewsStartPageUrl + button", + ], + }, + { + checkboxID: "alwaysCheckDefault", + pref: "mail.shell.checkDefaultClient", + } + ); +}); + +add_task(async () => { + await testCheckboxes( + "paneGeneral", + "scrollingGroup", + { + checkboxID: "useAutoScroll", + pref: "general.autoScroll", + }, + { + checkboxID: "useSmoothScrolling", + pref: "general.smoothScroll", + } + ); +}); + +add_task(async () => { + await testCheckboxes( + "paneGeneral", + "enableGloda", + { + checkboxID: "enableGloda", + pref: "mailnews.database.global.indexer.enabled", + }, + { + checkboxID: "allowHWAccel", + pref: "layers.acceleration.disabled", + prefValues: [true, false], + } + ); +}); + +add_task(async () => { + if (AppConstants.platform != "macosx") { + await testCheckboxes( + "paneGeneral", + "incomingMailCategory", + { + checkboxID: "newMailNotification", + pref: "mail.biff.play_sound", + enabledElements: ["#soundType radio"], + }, + { + checkboxID: "newMailNotificationAlert", + pref: "mail.biff.show_alert", + enabledElements: ["#customizeMailAlert"], + } + ); + } +}); + +add_task(async () => { + if (AppConstants.platform == "macosx") { + return; + } + + Services.prefs.setBoolPref("mail.biff.play_sound", true); + + await testRadioButtons("paneGeneral", "incomingMailCategory", { + pref: "mail.biff.play_sound.type", + states: [ + { + id: "system", + prefValue: 0, + }, + { + id: "custom", + prefValue: 1, + enabledElements: ["#soundUrlLocation", "#browseForSound"], + }, + ], + }); +}); + +add_task(async () => { + await testCheckboxes("paneGeneral", "fontsGroup", { + checkboxID: "displayGlyph", + pref: "mail.display_glyph", + }); + + await testCheckboxes( + "paneGeneral", + "readingAndDisplayCategory", + { + checkboxID: "automaticallyMarkAsRead", + pref: "mailnews.mark_message_read.auto", + enabledElements: ["#markAsReadAutoPreferences radio"], + }, + { + checkboxID: "closeMsgOnMoveOrDelete", + pref: "mail.close_message_window.on_delete", + }, + { + checkboxID: "showCondensedAddresses", + pref: "mail.showCondensedAddresses", + } + ); +}); + +add_task(async () => { + Services.prefs.setBoolPref("mailnews.mark_message_read.auto", true); + + await testRadioButtons( + "paneGeneral", + "mark_read_immediately", + { + pref: "mailnews.mark_message_read.delay", + states: [ + { + id: "mark_read_immediately", + prefValue: false, + }, + { + id: "markAsReadAfterDelay", + prefValue: true, + enabledElements: ["#markAsReadDelay"], + }, + ], + }, + { + pref: "mail.openMessageBehavior", + states: [ + { + id: "newTab", + prefValue: 2, + }, + { + id: "newWindow", + prefValue: 0, + }, + { + id: "existingWindow", + prefValue: 1, + }, + ], + } + ); +}); + +add_task(async () => { + // We don't want to wake up the platform search for this test. + // if (AppConstants.platform == "macosx") { + // tests.push({ + // checkboxID: "searchIntegration", + // pref: "mail.spotlight.enable", + // }); + // } else if (AppConstants.platform == "win") { + // tests.push({ + // checkboxID: "searchIntegration", + // pref: "mail.winsearch.enable", + // }); + // } + + await testCheckboxes( + "paneGeneral", + "allowSmartSize", + { + checkboxID: "allowSmartSize", + pref: "browser.cache.disk.smart_size.enabled", + prefValues: [true, false], + enabledElements: ["#cacheSize"], + }, + { + checkboxID: "offlineCompactFolder", + pref: "mail.prompt_purge_threshhold", + enabledElements: [ + "#offlineCompactFolderMin", + "#offlineCompactFolderAutomatically", + ], + } + ); +}); + +add_task(async () => { + await testRadioButtons("paneGeneral", "formatLocale", { + pref: "intl.regional_prefs.use_os_locales", + states: [ + { + id: "appLocale", + prefValue: false, + }, + { + id: "rsLocale", + prefValue: true, + }, + ], + }); +}); + +add_task(async () => { + await testRadioButtons("paneGeneral", "filesAttachmentCategory", { + pref: "browser.download.useDownloadDir", + states: [ + { + id: "saveTo", + prefValue: true, + enabledElements: ["#downloadFolder", "#chooseFolder"], + }, + { + id: "alwaysAsk", + prefValue: false, + }, + ], + }); +}); + +add_task(async function testTagDialog() { + const { prefsDocument, prefsWindow } = await openNewPrefsTab( + "paneGeneral", + "tagsCategory" + ); + + let newTagDialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/preferences/tagDialog.xhtml", + { + isSubDialog: true, + async callback(dialogWindow) { + await TestUtils.waitForCondition( + () => Services.focus.focusedWindow == dialogWindow, + "waiting for subdialog to be focused" + ); + + const dialogDocument = dialogWindow.document; + + EventUtils.sendString("tbird", dialogWindow); + // "#000080" == rgb(0, 0, 128); + dialogDocument.getElementById("tagColorPicker").value = "#000080"; + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.querySelector("dialog").getButton("accept"), + {}, + dialogWindow + ); + await new Promise(r => setTimeout(r)); + }, + } + ); + + let newTagButton = prefsDocument.getElementById("newTagButton"); + EventUtils.synthesizeMouseAtCenter(newTagButton, {}, prefsWindow); + await newTagDialogPromise; + + let tagList = prefsDocument.getElementById("tagList"); + + Assert.ok( + tagList.querySelector('richlistitem[value="tbird"]'), + "new tbird tag should be in the list" + ); + Assert.equal( + tagList.querySelector('richlistitem[value="tbird"]').style.color, + "rgb(0, 0, 128)", + "tbird tag color should be correct" + ); + Assert.equal( + tagList.querySelectorAll('richlistitem[value="tbird"]').length, + 1, + "new tbird tag should be in the list exactly once" + ); + + Assert.equal( + tagList.querySelector('richlistitem[value="tbird"]'), + tagList.selectedItem, + "tbird tag should be selected" + ); + + // Now edit the tag. The key should stay the same, name and color will change. + + let editTagDialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/preferences/tagDialog.xhtml", + { + isSubDialog: true, + async callback(dialogWindow) { + await TestUtils.waitForCondition( + () => Services.focus.focusedWindow == dialogWindow, + "waiting for subdialog to be focused" + ); + + const dialogDocument = dialogWindow.document; + + Assert.equal( + dialogDocument.getElementById("name").value, + "tbird", + "should have existing tbird tag name prefilled" + ); + Assert.equal( + dialogDocument.getElementById("tagColorPicker").value, + "#000080", + "should have existing tbird tag color prefilled" + ); + + EventUtils.sendString("-xx", dialogWindow); // => tbird-xx + // "#FFD700" == rgb(255, 215, 0); + dialogDocument.getElementById("tagColorPicker").value = "#FFD700"; + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.querySelector("dialog").getButton("accept"), + {}, + dialogWindow + ); + await new Promise(r => setTimeout(r)); + }, + } + ); + + let editTagButton = prefsDocument.getElementById("editTagButton"); + EventUtils.synthesizeMouseAtCenter(editTagButton, {}, prefsWindow); + await editTagDialogPromise; + + Assert.ok( + tagList.querySelector( + 'richlistitem[value="tbird"] > label[value="tbird-xx"]' + ), + "tbird-xx tag should be in the list" + ); + Assert.equal( + tagList.querySelector('richlistitem[value="tbird"]').style.color, + "rgb(255, 215, 0)", + "tbird-xx tag color should be correct" + ); + Assert.equal( + tagList.querySelectorAll('richlistitem[value="tbird"]').length, + 1, + "tbird-xx tag should be in the list exactly once" + ); + + // And remove it. + + EventUtils.synthesizeMouseAtCenter( + prefsDocument.getElementById("removeTagButton"), + {}, + prefsWindow + ); + await new Promise(r => setTimeout(r)); + + Assert.equal( + tagList.querySelector('richlistitem[value="tbird"]'), + null, + "tbird-xx (with key tbird) tag should have been removed from the list" + ); + + await closePrefsTab(); +}); diff --git a/comm/mail/components/preferences/test/browser/browser_openPreferences.js b/comm/mail/components/preferences/test/browser/browser_openPreferences.js new file mode 100644 index 0000000000..01aceb085a --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser_openPreferences.js @@ -0,0 +1,37 @@ +/* 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/. */ + +function getStoredLastSelected() { + return Services.xulStore.getValue( + "about:preferences", + "paneDeck", + "lastSelected" + ); +} + +add_task(async () => { + // Check that openPreferencesTab with no arguments and no stored value opens the first pane. + Services.xulStore.removeDocument("about:preferences"); + + let { prefsWindow } = await openNewPrefsTab(); + is(prefsWindow.gLastCategory.category, "paneGeneral"); + + await closePrefsTab(); +}); + +add_task(async () => { + // Check that openPreferencesTab with one argument opens the right pane… + Services.xulStore.removeDocument("about:preferences"); + + await openNewPrefsTab("panePrivacy"); + is(getStoredLastSelected(), "panePrivacy"); + + await closePrefsTab(); + + // … even with a value in the XULStore. + await openNewPrefsTab("paneCompose"); + is(getStoredLastSelected(), "paneCompose"); + + await closePrefsTab(); +}); diff --git a/comm/mail/components/preferences/test/browser/browser_privacy.js b/comm/mail/components/preferences/test/browser/browser_privacy.js new file mode 100644 index 0000000000..1b91ec35e8 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser_privacy.js @@ -0,0 +1,454 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async () => { + await testCheckboxes( + "panePrivacy", + "privacyCategory", + { + checkboxID: "acceptRemoteContent", + pref: "mailnews.message_display.disable_remote_image", + prefValues: [true, false], + }, + { + checkboxID: "keepHistory", + pref: "places.history.enabled", + }, + { + checkboxID: "acceptCookies", + pref: "network.cookie.cookieBehavior", + prefValues: [2, 0], + enabledElements: ["#acceptThirdPartyMenu"], + unaffectedElements: ["#cookieExceptions"], + }, + { + checkboxID: "privacyDoNotTrackCheckbox", + pref: "privacy.donottrackheader.enabled", + } + ); +}); + +add_task(async () => { + await testCheckboxes( + "panePrivacy", + "privacyJunkCategory", + { + checkboxID: "manualMark", + pref: "mail.spam.manualMark", + enabledElements: ["#manualMarkMode radio"], + }, + { + checkboxID: "markAsReadOnSpam", + pref: "mail.spam.markAsReadOnSpam", + }, + { + checkboxID: "enableJunkLogging", + pref: "mail.spam.logging.enabled", + enabledElements: ["#openJunkLogButton"], + } + ); + + await testCheckboxes("panePrivacy", "privacySecurityCategory", { + checkboxID: "enablePhishingDetector", + pref: "mail.phishing.detection.enabled", + }); + + await testCheckboxes("panePrivacy", "enableAntiVirusQuarantine", { + checkboxID: "enableAntiVirusQuarantine", + pref: "mailnews.downloadToTempFile", + }); +}); + +add_task(async () => { + Services.prefs.setBoolPref("mail.spam.manualMark", true); + + await testRadioButtons("panePrivacy", "privacyJunkCategory", { + pref: "mail.spam.manualMarkMode", + states: [ + { + id: "manualMarkMode0", + prefValue: 0, + }, + { + id: "manualMarkMode1", + prefValue: 1, + }, + ], + }); +}); + +add_task(async () => { + // Telemetry pref is locked. + // await testCheckboxes("paneAdvanced", undefined, { + // checkboxID: "submitTelemetryBox", + // pref: "toolkit.telemetry.enabled", + // }); + + await testCheckboxes("panePrivacy", "enableOCSP", { + checkboxID: "enableOCSP", + pref: "security.OCSP.enabled", + prefValues: [0, 1], + }); +}); + +// Here we'd test the update choices, but I don't want to go near that. +add_task(async () => { + await testRadioButtons("panePrivacy", "enableOCSP", { + pref: "security.default_personal_cert", + states: [ + { + id: "certSelectionAuto", + prefValue: "Select Automatically", + }, + { + id: "certSelectionAsk", + prefValue: "Ask Every Time", + }, + ], + }); +}); + +add_task(async function testRemoteContentDialog() { + const { prefsDocument, prefsWindow } = await openNewPrefsTab("panePrivacy"); + + let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/preferences/permissions.xhtml", + { + isSubDialog: true, + async callback(dialogWindow) { + await TestUtils.waitForCondition( + () => Services.focus.focusedWindow == dialogWindow, + "waiting for subdialog to be focused" + ); + + const dialogDocument = dialogWindow.document; + const url = dialogDocument.getElementById("url"); + const permissionsTree = + dialogDocument.getElementById("permissionsTree"); + + EventUtils.sendString("accept.invalid", dialogWindow); + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnAllow"), + {}, + dialogWindow + ); + await new Promise(f => setTimeout(f)); + Assert.equal(url.value, "", "url input should be cleared"); + Assert.equal( + permissionsTree.view.rowCount, + 1, + "new entry should be added to list" + ); + + Assert.ok( + BrowserTestUtils.is_hidden( + dialogDocument.getElementById("btnSession") + ), + "session button should be hidden" + ); + + EventUtils.sendString("block.invalid", dialogWindow); + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnBlock"), + {}, + dialogWindow + ); + await new Promise(f => setTimeout(f)); + Assert.equal(url.value, "", "url input should be cleared"); + Assert.equal( + permissionsTree.view.rowCount, + 2, + "new entry should be added to list" + ); + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnApplyChanges"), + {}, + dialogWindow + ); + }, + } + ); + let remoteContentExceptions = prefsDocument.getElementById( + "remoteContentExceptions" + ); + EventUtils.synthesizeMouseAtCenter(remoteContentExceptions, {}, prefsWindow); + await dialogPromise; + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let acceptURI = Services.io.newURI("http://accept.invalid/"); + let acceptPrincipal = Services.scriptSecurityManager.createContentPrincipal( + acceptURI, + {} + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(acceptPrincipal, "image"), + Ci.nsIPermissionManager.ALLOW_ACTION, + "accept permission should exist for accept.invalid" + ); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let blockURI = Services.io.newURI("http://block.invalid/"); + let blockPrincipal = Services.scriptSecurityManager.createContentPrincipal( + blockURI, + {} + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(blockPrincipal, "image"), + Ci.nsIPermissionManager.DENY_ACTION, + "block permission should exist for block.invalid" + ); + + dialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/preferences/permissions.xhtml", + { + isSubDialog: true, + async callback(dialogWindow) { + await TestUtils.waitForCondition( + () => Services.focus.focusedWindow == dialogWindow, + "waiting for subdialog to be focused" + ); + + const dialogDocument = dialogWindow.document; + const permissionsTree = + dialogDocument.getElementById("permissionsTree"); + + Assert.equal( + permissionsTree.view.rowCount, + 2, + "list should be populated" + ); + + permissionsTree.view.selection.select(0); + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("removePermission"), + {}, + dialogWindow + ); + Assert.equal( + permissionsTree.view.rowCount, + 1, + "row should be removed from list" + ); + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("removeAllPermissions"), + {}, + dialogWindow + ); + Assert.equal( + permissionsTree.view.rowCount, + 0, + "row should be removed from list" + ); + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnApplyChanges"), + {}, + dialogWindow + ); + }, + } + ); + EventUtils.synthesizeMouseAtCenter(remoteContentExceptions, {}, prefsWindow); + await dialogPromise; + + Assert.equal( + Services.perms.testPermissionFromPrincipal(acceptPrincipal, "image"), + Ci.nsIPermissionManager.UNKNOWN_ACTION, + "permission should be removed for accept.invalid" + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(blockPrincipal, "image"), + Ci.nsIPermissionManager.UNKNOWN_ACTION, + "permission should be removed for block.invalid" + ); + + await closePrefsTab(); +}); + +add_task(async function testCookiesDialog() { + const { prefsDocument, prefsWindow } = await openNewPrefsTab("panePrivacy"); + + let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/preferences/permissions.xhtml", + { + isSubDialog: true, + async callback(dialogWindow) { + await TestUtils.waitForCondition( + () => Services.focus.focusedWindow == dialogWindow, + "waiting for subdialog to be focused" + ); + + const dialogDocument = dialogWindow.document; + const url = dialogDocument.getElementById("url"); + const permissionsTree = + dialogDocument.getElementById("permissionsTree"); + + EventUtils.sendString("accept.invalid", dialogWindow); + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnAllow"), + {}, + dialogWindow + ); + await new Promise(f => setTimeout(f)); + Assert.equal(url.value, "", "url input should be cleared"); + Assert.equal( + permissionsTree.view.rowCount, + 1, + "new entry should be added to list" + ); + + EventUtils.sendString("session.invalid", dialogWindow); + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnSession"), + {}, + dialogWindow + ); + await new Promise(f => setTimeout(f)); + Assert.equal(url.value, "", "url input should be cleared"); + Assert.equal( + permissionsTree.view.rowCount, + 2, + "new entry should be added to list" + ); + + EventUtils.sendString("block.invalid", dialogWindow); + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnBlock"), + {}, + dialogWindow + ); + await new Promise(f => setTimeout(f)); + Assert.equal(url.value, "", "url input should be cleared"); + Assert.equal( + permissionsTree.view.rowCount, + 3, + "new entry should be added to list" + ); + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnApplyChanges"), + {}, + dialogWindow + ); + }, + } + ); + let cookieExceptions = prefsDocument.getElementById("cookieExceptions"); + EventUtils.synthesizeMouseAtCenter(cookieExceptions, {}, prefsWindow); + await dialogPromise; + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let acceptURI = Services.io.newURI("http://accept.invalid/"); + let acceptPrincipal = Services.scriptSecurityManager.createContentPrincipal( + acceptURI, + {} + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(acceptPrincipal, "cookie"), + Ci.nsIPermissionManager.ALLOW_ACTION, + "accept permission should exist for accept.invalid" + ); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let sessionURI = Services.io.newURI("http://session.invalid/"); + let sessionPrincipal = Services.scriptSecurityManager.createContentPrincipal( + sessionURI, + {} + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(sessionPrincipal, "cookie"), + Ci.nsICookiePermission.ACCESS_SESSION, + "session permission should exist for session.invalid" + ); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + let blockURI = Services.io.newURI("http://block.invalid/"); + let blockPrincipal = Services.scriptSecurityManager.createContentPrincipal( + blockURI, + {} + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(blockPrincipal, "cookie"), + Ci.nsIPermissionManager.DENY_ACTION, + "block permission should exist for block.invalid" + ); + + dialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/preferences/permissions.xhtml", + { + isSubDialog: true, + async callback(dialogWindow) { + await TestUtils.waitForCondition( + () => Services.focus.focusedWindow == dialogWindow, + "waiting for subdialog to be focused" + ); + + const dialogDocument = dialogWindow.document; + const permissionsTree = + dialogDocument.getElementById("permissionsTree"); + + Assert.equal( + permissionsTree.view.rowCount, + 3, + "list should be populated" + ); + + permissionsTree.view.selection.select(0); + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("removePermission"), + {}, + dialogWindow + ); + Assert.equal( + permissionsTree.view.rowCount, + 2, + "row should be removed from list" + ); + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("removeAllPermissions"), + {}, + dialogWindow + ); + Assert.equal( + permissionsTree.view.rowCount, + 0, + "row should be removed from list" + ); + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.getElementById("btnApplyChanges"), + {}, + dialogWindow + ); + }, + } + ); + EventUtils.synthesizeMouseAtCenter(cookieExceptions, {}, prefsWindow); + await dialogPromise; + + Assert.equal( + Services.perms.testPermissionFromPrincipal(acceptPrincipal, "cookie"), + Ci.nsIPermissionManager.UNKNOWN_ACTION, + "permission should be removed for accept.invalid" + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(sessionPrincipal, "cookie"), + Ci.nsIPermissionManager.UNKNOWN_ACTION, + "permission should be removed for session.invalid" + ); + Assert.equal( + Services.perms.testPermissionFromPrincipal(blockPrincipal, "cookie"), + Ci.nsIPermissionManager.UNKNOWN_ACTION, + "permission should be removed for block.invalid" + ); + + await closePrefsTab(); +}); diff --git a/comm/mail/components/preferences/test/browser/browser_sync.js b/comm/mail/components/preferences/test/browser/browser_sync.js new file mode 100644 index 0000000000..108695ebb5 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/browser_sync.js @@ -0,0 +1,419 @@ +/* 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 { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" +); + +const { FxAccounts } = ChromeUtils.importESModule( + "resource://gre/modules/FxAccounts.sys.mjs" +); +FxAccounts.config.promiseConnectAccountURI = entryPoint => + `https://example.org/?page=connect&entryPoint=${entryPoint}`; +FxAccounts.config.promiseManageURI = entryPoint => + `https://example.org/?page=manage&entryPoint=${entryPoint}`; +FxAccounts.config.promiseChangeAvatarURI = entryPoint => + `https://example.org/?page=avatar&entryPoint=${entryPoint}`; + +const ALL_ENGINES = [ + "accounts", + "identities", + "addressbooks", + "calendars", + "passwords", +]; +const PREF_PREFIX = "services.sync.engine"; + +let prefsWindow, prefsDocument, tabmail; + +add_setup(async function () { + for (let engine of ALL_ENGINES) { + Services.prefs.setBoolPref(`${PREF_PREFIX}.${engine}`, true); + } + + ({ prefsWindow, prefsDocument } = await openNewPrefsTab("paneSync")); + tabmail = document.getElementById("tabmail"); + + /** @implements {nsIExternalProtocolService} */ + let mockExternalProtocolService = { + QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]), + externalProtocolHandlerExists(protocolScheme) {}, + isExposedProtocol(protocolScheme) {}, + loadURI(uri, windowContext) { + Assert.report( + true, + undefined, + undefined, + `should not be opening ${uri.spec} in an external browser` + ); + }, + }; + + let mockExternalProtocolServiceCID = MockRegistrar.register( + "@mozilla.org/uriloader/external-protocol-service;1", + mockExternalProtocolService + ); + registerCleanupFunction(() => { + MockRegistrar.unregister(mockExternalProtocolServiceCID); + }); +}); + +add_task(async function testSectionStates() { + let noFxaAccount = prefsDocument.getElementById("noFxaAccount"); + let hasFxaAccount = prefsDocument.getElementById("hasFxaAccount"); + let accountStates = [noFxaAccount, hasFxaAccount]; + + let fxaLoginUnverified = prefsDocument.getElementById("fxaLoginUnverified"); + let fxaLoginRejected = prefsDocument.getElementById("fxaLoginRejected"); + let fxaLoginVerified = prefsDocument.getElementById("fxaLoginVerified"); + let loginStates = [fxaLoginUnverified, fxaLoginRejected, fxaLoginVerified]; + + let fxaDeviceInfo = prefsDocument.getElementById("fxaDeviceInfo"); + let syncConnected = prefsDocument.getElementById("syncConnected"); + let syncDisconnected = prefsDocument.getElementById("syncDisconnected"); + let syncStates = [syncConnected, syncDisconnected]; + + function assertStateVisible(states, visibleState) { + for (let state of states) { + let visible = BrowserTestUtils.is_visible(state); + Assert.equal( + visible, + state == visibleState, + `${state.id} should be ${state == visibleState ? "visible" : "hidden"}` + ); + } + } + + function checkStates({ + accountState, + loginState = null, + deviceInfoVisible = false, + syncState = null, + }) { + prefsWindow.gSyncPane.updateWeavePrefs(); + assertStateVisible(accountStates, accountState); + assertStateVisible(loginStates, loginState); + Assert.equal( + BrowserTestUtils.is_visible(fxaDeviceInfo), + deviceInfoVisible, + `fxaDeviceInfo should be ${deviceInfoVisible ? "visible" : "hidden"}` + ); + assertStateVisible(syncStates, syncState); + } + + async function assertTabOpens(target, expectedURL) { + if (typeof target == "string") { + target = prefsDocument.getElementById(target); + } + + let tabPromise = BrowserTestUtils.waitForEvent(window, "TabOpen"); + EventUtils.synthesizeMouseAtCenter(target, {}, prefsWindow); + await tabPromise; + let tab = tabmail.currentTabInfo; + await BrowserTestUtils.browserLoaded(tab.browser); + Assert.equal( + tab.browser.currentURI.spec, + `https://example.org/${expectedURL}`, + "a tab opened to the correct URL" + ); + tabmail.closeTab(tab); + } + + info("No account"); + Assert.equal(prefsWindow.UIState.get().status, "not_configured"); + checkStates({ accountState: noFxaAccount }); + + // Check clicking the Sign In button opens the connect page in a tab. + await assertTabOpens("noFxaSignIn", "?page=connect&entryPoint="); + + // Override the window's UIState object with mock values. + let baseState = { + email: "test@invalid", + displayName: "Testy McTest", + avatarURL: + "https://example.org/browser/comm/mail/components/preferences/test/browser/files/avatar.png", + avatarIsDefault: false, + }; + let mockState; + prefsWindow.UIState = { + ON_UPDATE: "sync-ui-state:update", + STATUS_LOGIN_FAILED: "login_failed", + STATUS_NOT_CONFIGURED: "not_configured", + STATUS_NOT_VERIFIED: "not_verified", + STATUS_SIGNED_IN: "signed_in", + get() { + return mockState; + }, + }; + + info("Login not verified"); + mockState = { ...baseState, status: "not_verified" }; + checkStates({ + accountState: hasFxaAccount, + loginState: fxaLoginUnverified, + deviceInfoVisible: true, + }); + Assert.deepEqual( + await prefsDocument.l10n.getAttributes( + prefsDocument.getElementById("fxaAccountMailNotVerified") + ), + { + id: "sync-pane-email-not-verified", + args: { userEmail: "test@invalid" }, + }, + "email address set correctly" + ); + + // Untested: Resend and remove account buttons. + + info("Login rejected"); + mockState = { ...baseState, status: "login_failed" }; + checkStates({ + accountState: hasFxaAccount, + loginState: fxaLoginRejected, + deviceInfoVisible: true, + }); + Assert.deepEqual( + await prefsDocument.l10n.getAttributes( + prefsDocument.getElementById("fxaAccountLoginRejected") + ), + { + id: "sync-signedin-login-failure", + args: { userEmail: "test@invalid" }, + }, + "email address set correctly" + ); + + // Untested: Sign in and remove account buttons. + + info("Logged in, sync disabled"); + mockState = { ...baseState, status: "verified", syncEnabled: false }; + checkStates({ + accountState: hasFxaAccount, + loginState: fxaLoginVerified, + deviceInfoVisible: true, + syncState: syncDisconnected, + }); + let photo = fxaLoginVerified.querySelector(".contact-photo"); + Assert.equal( + photo.src, + "https://example.org/browser/comm/mail/components/preferences/test/browser/files/avatar.png", + "avatar image set correctly" + ); + + // Check clicking the avatar image opens the avatar page in a tab. + await assertTabOpens(photo, "?page=avatar&entryPoint=preferences"); + + Assert.equal( + prefsDocument.getElementById("fxaDisplayName").textContent, + "Testy McTest", + "display name set correctly" + ); + Assert.equal( + prefsDocument.getElementById("fxaEmailAddress").textContent, + "test@invalid", + "email address set correctly" + ); + + // Check clicking the management link opens the management page in a tab. + await assertTabOpens("verifiedManage", "?page=manage&entryPoint=preferences"); + + // Untested: Sign out button. + + info("Device name section"); + let deviceNameInput = prefsDocument.getElementById("fxaDeviceNameInput"); + let deviceNameCancel = prefsDocument.getElementById("fxaDeviceNameCancel"); + let deviceNameSave = prefsDocument.getElementById("fxaDeviceNameSave"); + let deviceNameChange = prefsDocument.getElementById( + "fxaDeviceNameChangeDeviceName" + ); + Assert.ok(deviceNameInput.readOnly, "input is read-only"); + Assert.ok( + BrowserTestUtils.is_hidden(deviceNameCancel), + "cancel button is hidden" + ); + Assert.ok( + BrowserTestUtils.is_hidden(deviceNameSave), + "save button is hidden" + ); + Assert.ok( + BrowserTestUtils.is_visible(deviceNameChange), + "change button is visible" + ); + + EventUtils.synthesizeMouseAtCenter(deviceNameChange, {}, prefsWindow); + Assert.ok(!deviceNameInput.readOnly, "input is writeable"); + Assert.equal(prefsDocument.activeElement, deviceNameInput, "input is active"); + Assert.ok( + BrowserTestUtils.is_visible(deviceNameCancel), + "cancel button is visible" + ); + Assert.ok( + BrowserTestUtils.is_visible(deviceNameSave), + "save button is visible" + ); + Assert.ok( + BrowserTestUtils.is_hidden(deviceNameChange), + "change button is hidden" + ); + + EventUtils.synthesizeMouseAtCenter(deviceNameCancel, {}, prefsWindow); + Assert.ok(deviceNameInput.readOnly, "input is read-only"); + Assert.ok( + BrowserTestUtils.is_hidden(deviceNameCancel), + "cancel button is hidden" + ); + Assert.ok( + BrowserTestUtils.is_hidden(deviceNameSave), + "save button is hidden" + ); + Assert.ok( + BrowserTestUtils.is_visible(deviceNameChange), + "change button is visible" + ); + + // Check the turn on sync button works. + await openEngineDialog({ expectEngines: ALL_ENGINES, button: "syncSetup" }); + + info("Logged in, sync enabled"); + mockState = { ...baseState, status: "verified", syncEnabled: true }; + checkStates({ + accountState: hasFxaAccount, + loginState: fxaLoginVerified, + deviceInfoVisible: true, + syncState: syncConnected, + }); + + // Untested: Sync now button. + + // Check the learn more link opens a tab. + await assertTabOpens("enginesLearnMore", "?page=learnMore"); + + // Untested: Disconnect button. +}); + +add_task(async function testEngines() { + function assertEnginesEnabled(...expectedEnabled) { + for (let engine of ALL_ENGINES) { + let enabled = Services.prefs.getBoolPref(`${PREF_PREFIX}.${engine}`); + Assert.equal( + enabled, + expectedEnabled.includes(engine), + `${engine} should be ${ + expectedEnabled.includes(engine) ? "enabled" : "disabled" + }` + ); + } + } + + function assertEnginesShown(...expectEngines) { + let ENGINES_TO_ITEMS = { + accounts: "showSyncAccount", + identities: "showSyncIdentity", + addressbooks: "showSyncAddress", + calendars: "showSyncCalendar", + passwords: "showSyncPasswords", + }; + let expectItems = expectEngines.map(engine => ENGINES_TO_ITEMS[engine]); + let items = Array.from( + prefsDocument.querySelectorAll("#showSyncedList > li:not([hidden])"), + li => li.id + ); + Assert.deepEqual(items, expectItems, "enabled engines shown correctly"); + } + + assertEnginesShown(...ALL_ENGINES); + Services.prefs.setBoolPref(`${PREF_PREFIX}.accounts`, false); + assertEnginesShown("identities", "addressbooks", "calendars", "passwords"); + Services.prefs.setBoolPref(`${PREF_PREFIX}.identities`, false); + Services.prefs.setBoolPref(`${PREF_PREFIX}.addressbooks`, false); + Services.prefs.setBoolPref(`${PREF_PREFIX}.calendars`, false); + assertEnginesShown("passwords"); + Services.prefs.setBoolPref(`${PREF_PREFIX}.passwords`, false); + assertEnginesShown(); + + info("Checking the engine selection dialog"); + await openEngineDialog({ + toggleEngines: ["accounts", "identities", "passwords"], + }); + + assertEnginesEnabled("accounts", "identities", "passwords"); + assertEnginesShown("accounts", "identities", "passwords"); + + await openEngineDialog({ + expectEngines: ["accounts", "identities", "passwords"], + toggleEngines: ["calendars", "passwords"], + action: "cancel", + }); + + assertEnginesEnabled("accounts", "identities", "passwords"); + assertEnginesShown("accounts", "identities", "passwords"); + + await openEngineDialog({ + expectEngines: ["accounts", "identities", "passwords"], + toggleEngines: ["calendars", "passwords"], + action: "accept", + }); + + assertEnginesEnabled("accounts", "identities", "calendars"); + assertEnginesShown("accounts", "identities", "calendars"); + + Services.prefs.setBoolPref(`${PREF_PREFIX}.addressbooks`, true); + Services.prefs.setBoolPref(`${PREF_PREFIX}.passwords`, true); + assertEnginesShown(...ALL_ENGINES); +}); + +async function openEngineDialog({ + expectEngines = [], + toggleEngines = [], + action = "accept", + button = "syncChangeOptions", +}) { + const ENGINES_TO_CHECKBOXES = { + accounts: "configSyncAccount", + identities: "configSyncIdentity", + addressbooks: "configSyncAddress", + calendars: "configSyncCalendar", + passwords: "configSyncPasswords", + }; + let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + undefined, + "chrome://messenger/content/preferences/syncDialog.xhtml", + { isSubDialog: true } + ); + EventUtils.synthesizeMouseAtCenter( + prefsDocument.getElementById(button), + {}, + prefsWindow + ); + let dialogWindow = await dialogPromise; + let dialogDocument = dialogWindow.document; + await new Promise(resolve => dialogWindow.setTimeout(resolve)); + + let expectItems = expectEngines.map(engine => ENGINES_TO_CHECKBOXES[engine]); + + let checkedItems = Array.from( + dialogDocument.querySelectorAll(`input[type="checkbox"]`) + ) + .filter(cb => cb.checked) + .map(cb => cb.id); + Assert.deepEqual( + checkedItems, + expectItems, + "enabled engines checked correctly" + ); + + for (let toggleItem of toggleEngines) { + let checkbox = dialogDocument.getElementById( + ENGINES_TO_CHECKBOXES[toggleItem] + ); + checkbox.checked = !checkbox.checked; + } + + EventUtils.synthesizeMouseAtCenter( + dialogDocument.querySelector("dialog").getButton(action), + {}, + dialogWindow + ); +} diff --git a/comm/mail/components/preferences/test/browser/files/avatar.png b/comm/mail/components/preferences/test/browser/files/avatar.png Binary files differnew file mode 100644 index 0000000000..ca0894316a --- /dev/null +++ b/comm/mail/components/preferences/test/browser/files/avatar.png diff --git a/comm/mail/components/preferences/test/browser/files/icon.svg b/comm/mail/components/preferences/test/browser/files/icon.svg new file mode 100644 index 0000000000..6c1a552445 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/files/icon.svg @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <circle cx="8" cy="8" r="7.5" fill="#ffffff" stroke="#00aa00" stroke-width="1.5"/> + <circle cx="5" cy="6" r="1.5" fill="#00aa00"/> + <circle cx="11" cy="6" r="1.5" fill="#00aa00"/> + <path d="M 12.83,9.30 C 12.24,11.48 10.26,13 8,13 5.75,13 3.74,11.48 3.17,9.29" fill="none" stroke="#00aa00" stroke-width="1.5"/> +</svg> diff --git a/comm/mail/components/preferences/test/browser/files/management.html b/comm/mail/components/preferences/test/browser/files/management.html new file mode 100644 index 0000000000..7e3561d823 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/files/management.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"/> + <title></title> +</head> +<body> + <a id="a" href="https://www.example.com/">Click me!</a> +</body> +</html> diff --git a/comm/mail/components/preferences/test/browser/head.js b/comm/mail/components/preferences/test/browser/head.js new file mode 100644 index 0000000000..12cbdb17f1 --- /dev/null +++ b/comm/mail/components/preferences/test/browser/head.js @@ -0,0 +1,314 @@ +/* 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/. */ + +/* import-globals-from ../../../../base/content/utilityOverlay.js */ + +async function openNewPrefsTab(paneID, scrollPaneTo, otherArgs) { + let tabmail = document.getElementById("tabmail"); + let prefsTabMode = tabmail.tabModes.preferencesTab; + + is(prefsTabMode.tabs.length, 0, "Prefs tab is not open"); + + let prefsDocument = await new Promise(resolve => { + Services.obs.addObserver(function documentLoaded(subject) { + if (subject.URL.startsWith("about:preferences")) { + Services.obs.removeObserver(documentLoaded, "chrome-document-loaded"); + subject.ownerGlobal.setTimeout(() => resolve(subject)); + } + }, "chrome-document-loaded"); + openPreferencesTab(paneID, scrollPaneTo, otherArgs); + }); + ok(prefsDocument.URL.startsWith("about:preferences"), "Prefs tab is open"); + + prefsDocument = prefsTabMode.tabs[0].browser.contentDocument; + let prefsWindow = prefsDocument.ownerGlobal; + prefsWindow.resizeTo(screen.availWidth, screen.availHeight); + + if (paneID) { + await new Promise(resolve => prefsWindow.setTimeout(resolve)); + is( + prefsWindow.gLastCategory.category, + paneID, + `Selected pane is ${paneID}` + ); + } else { + // If we don't wait here for other scripts to run, they + // could be in a bad state if our test closes the tab. + await new Promise(resolve => prefsWindow.setTimeout(resolve)); + } + + registerCleanupOnce(); + + await new Promise(resolve => prefsWindow.setTimeout(resolve)); + let container = prefsDocument.getElementById("preferencesContainer"); + if (scrollPaneTo && container.scrollHeight > container.clientHeight) { + Assert.greater( + container.scrollTop, + 0, + "Prefs page did scroll when it was supposed to" + ); + } + return { prefsDocument, prefsWindow }; +} + +async function openExistingPrefsTab(paneID, scrollPaneTo, otherArgs) { + let tabmail = document.getElementById("tabmail"); + let prefsTabMode = tabmail.tabModes.preferencesTab; + + is(prefsTabMode.tabs.length, 1, "Prefs tab is open"); + + let prefsDocument = prefsTabMode.tabs[0].browser.contentDocument; + let prefsWindow = prefsDocument.ownerGlobal; + prefsWindow.resizeTo(screen.availWidth, screen.availHeight); + + if (paneID && prefsWindow.gLastCategory.category != paneID) { + openPreferencesTab(paneID, scrollPaneTo, otherArgs); + } + + await new Promise(resolve => prefsWindow.setTimeout(resolve)); + is(prefsWindow.gLastCategory.category, paneID, `Selected pane is ${paneID}`); + + if (scrollPaneTo) { + Assert.greater( + prefsDocument.getElementById("preferencesContainer").scrollTop, + 0, + "Prefs page did scroll when it was supposed to" + ); + } + + registerCleanupOnce(); + return { prefsDocument, prefsWindow }; +} + +function registerCleanupOnce() { + if (registerCleanupOnce.alreadyRegistered) { + return; + } + registerCleanupFunction(closePrefsTab); + registerCleanupOnce.alreadyRegistered = true; +} + +async function closePrefsTab() { + info("Closing prefs tab"); + let tabmail = document.getElementById("tabmail"); + let prefsTab = tabmail.tabModes.preferencesTab.tabs[0]; + if (prefsTab) { + tabmail.closeTab(prefsTab); + } +} + +/** + * Tests a checkbox sets the preference is set in the right state when the preferences tab opens, + * that the preference it relates to is set properly, and any UI elements that should be disabled + * by it are disabled. + * + * Each of the tests arguments is an object describing a test, containing: + * checkboxID - the ID of the checkbox to test + * pref - the name of a preference, + * prefValues - an array of two values: pref value when not checked, pref value when checked + * (optional, defaults to [false, true]) + * enabledElements - an array of CSS selectors (optional) + * enabledInverted - if the elements should be disabled when the checkbox is checked (optional) + * unaffectedElements - array of CSS selectors that should not be affected by + * the toggling of the checkbox. + */ +async function testCheckboxes(paneID, scrollPaneTo, ...tests) { + for (let initiallyChecked of [true, false]) { + info(`Opening ${paneID} with prefs set to ${initiallyChecked}`); + + for (let test of tests) { + let wantedValue = initiallyChecked; + if (test.prefValues) { + wantedValue = wantedValue ? test.prefValues[1] : test.prefValues[0]; + } + if (typeof wantedValue == "number") { + Services.prefs.setIntPref(test.pref, wantedValue); + } else { + Services.prefs.setBoolPref(test.pref, wantedValue); + } + } + + let { prefsDocument, prefsWindow } = await openNewPrefsTab( + paneID, + scrollPaneTo + ); + + let testUIState = function (test, checked) { + let wantedValue = checked; + if (test.prefValues) { + wantedValue = wantedValue ? test.prefValues[1] : test.prefValues[0]; + } + let checkbox = prefsDocument.getElementById(test.checkboxID); + is( + checkbox.checked, + checked, + wantedValue, + "Checkbox " + (checked ? "is" : "isn't") + " checked" + ); + if (typeof wantedValue == "number") { + is( + Services.prefs.getIntPref(test.pref, -999), + wantedValue, + `Pref is ${wantedValue}` + ); + } else { + is( + Services.prefs.getBoolPref(test.pref), + wantedValue, + `Pref is ${wantedValue}` + ); + } + + if (test.enabledElements) { + let disabled = checked; + if (test.enabledInverted) { + disabled = !disabled; + } + for (let selector of test.enabledElements) { + let elements = prefsDocument.querySelectorAll(selector); + ok( + elements.length >= 1, + `At least one element matched '${selector}'` + ); + for (let element of elements) { + is( + element.disabled, + !disabled, + "Element " + (disabled ? "isn't" : "is") + " disabled" + ); + } + } + } + }; + + let testUnaffected = function (ids, states) { + ids.forEach((sel, index) => { + let isOk = prefsDocument.querySelector(sel).disabled === states[index]; + is(isOk, true, `Element "${sel}" is unaffected`); + }); + }; + + for (let test of tests) { + info(`Checking ${test.checkboxID}`); + + let unaffectedSelectors = test.unaffectedElements || []; + let unaffectedStates = unaffectedSelectors.map( + sel => prefsDocument.querySelector(sel).disabled + ); + + let checkbox = prefsDocument.getElementById(test.checkboxID); + checkbox.scrollIntoView(false); + testUIState(test, initiallyChecked); + + EventUtils.synthesizeMouseAtCenter(checkbox, {}, prefsWindow); + testUIState(test, !initiallyChecked); + testUnaffected(unaffectedSelectors, unaffectedStates); + + EventUtils.synthesizeMouseAtCenter(checkbox, {}, prefsWindow); + testUIState(test, initiallyChecked); + testUnaffected(unaffectedSelectors, unaffectedStates); + } + + await closePrefsTab(); + } +} + +/** + * Tests a set of radio buttons is in the right state when the preferences tab opens, and when + * the selected button changes that the preference it relates to is set properly, and any related + * UI elements that should be disabled are disabled. + * + * Each of the tests arguments is an object describing a test, containing: + * pref - the name of an integer preference, + * states - an array with each element describing a radio button: + * id - the ID of the button to test, + * prefValue - the value the pref should be set to + * enabledElements - an array of CSS selectors to elements that should be enabled when this + * radio button is selected (optional) + */ +async function testRadioButtons(paneID, scrollPaneTo, ...tests) { + for (let { pref, states } of tests) { + for (let initialState of states) { + info(`Opening ${paneID} with ${pref} set to ${initialState.prefValue}`); + + if (typeof initialState.prefValue == "number") { + Services.prefs.setIntPref(pref, initialState.prefValue); + } else if (typeof initialState.prefValue == "boolean") { + Services.prefs.setBoolPref(pref, initialState.prefValue); + } else { + Services.prefs.setCharPref(pref, initialState.prefValue); + } + + let { prefsDocument, prefsWindow } = await openNewPrefsTab( + paneID, + scrollPaneTo + ); + + let testUIState = function (currentState) { + info(`Testing with ${pref} set to ${currentState.prefValue}`); + for (let state of states) { + let isCurrentState = state == currentState; + let radio = prefsDocument.getElementById(state.id); + is(radio.selected, isCurrentState, `${state.id}.selected`); + + if (state.enabledElements) { + for (let selector of state.enabledElements) { + let elements = prefsDocument.querySelectorAll(selector); + ok( + elements.length >= 1, + `At least one element matched '${selector}'` + ); + for (let element of elements) { + is( + element.disabled, + !isCurrentState, + "Element " + (isCurrentState ? "isn't" : "is") + " disabled" + ); + } + } + } + } + if (typeof initialState.prefValue == "number") { + is( + Services.prefs.getIntPref(pref, -999), + currentState.prefValue, + `Pref is ${currentState.prefValue}` + ); + } else if (typeof initialState.prefValue == "boolean") { + is( + Services.prefs.getBoolPref(pref), + currentState.prefValue, + `Pref is ${currentState.prefValue}` + ); + } else { + is( + Services.prefs.getCharPref(pref, "FAKE VALUE"), + currentState.prefValue, + `Pref is ${currentState.prefValue}` + ); + } + }; + + // Check the initial setup is correct. + testUIState(initialState); + // Cycle through possible values, checking each one. + for (let state of states) { + if (state == initialState) { + continue; + } + let radio = prefsDocument.getElementById(state.id); + radio.scrollIntoView(false); + EventUtils.synthesizeMouseAtCenter(radio, {}, prefsWindow); + testUIState(state); + } + // Go back to the initial value. + let initialRadio = prefsDocument.getElementById(initialState.id); + initialRadio.scrollIntoView(false); + EventUtils.synthesizeMouseAtCenter(initialRadio, {}, prefsWindow); + testUIState(initialState); + + await closePrefsTab(); + } + } +} |