From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser/browser_ext_compose_saveTemplate.js | 432 +++++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 comm/mail/components/extensions/test/browser/browser_ext_compose_saveTemplate.js (limited to 'comm/mail/components/extensions/test/browser/browser_ext_compose_saveTemplate.js') diff --git a/comm/mail/components/extensions/test/browser/browser_ext_compose_saveTemplate.js b/comm/mail/components/extensions/test/browser/browser_ext_compose_saveTemplate.js new file mode 100644 index 0000000000..d9ce180011 --- /dev/null +++ b/comm/mail/components/extensions/test/browser/browser_ext_compose_saveTemplate.js @@ -0,0 +1,432 @@ +/* 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 { ExtensionSupport } = ChromeUtils.import( + "resource:///modules/ExtensionSupport.jsm" +); +var { localAccountUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/LocalAccountUtils.jsm" +); +// Import the smtp server scripts +var { + nsMailServer, + gThreadManager, + fsDebugNone, + fsDebugAll, + fsDebugRecv, + fsDebugRecvSend, +} = ChromeUtils.import("resource://testing-common/mailnews/Maild.jsm"); +var { SmtpDaemon, SMTP_RFC2821_handler } = ChromeUtils.import( + "resource://testing-common/mailnews/Smtpd.jsm" +); +var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import( + "resource://testing-common/mailnews/Auth.jsm" +); + +// Setup the daemon and server +function setupServerDaemon(handler) { + if (!handler) { + handler = function (d) { + return new SMTP_RFC2821_handler(d); + }; + } + var server = new nsMailServer(handler, new SmtpDaemon()); + return server; +} + +function getBasicSmtpServer(port = 1, hostname = "localhost") { + let server = localAccountUtils.create_outgoing_server( + port, + "user", + "password", + hostname + ); + + // Override the default greeting so we get something predictable + // in the ELHO message + Services.prefs.setCharPref("mail.smtpserver.default.hello_argument", "test"); + + return server; +} + +function getSmtpIdentity(senderName, smtpServer) { + // Set up the identity. + let identity = MailServices.accounts.createIdentity(); + identity.email = senderName; + identity.smtpServerKey = smtpServer.key; + + return identity; +} + +var gServer; +var gLocalRootFolder; +let gPopAccount; +let gLocalAccount; + +add_setup(() => { + gServer = setupServerDaemon(); + gServer.start(); + + // Test needs a non-local default account to be able to send messages. + gPopAccount = createAccount("pop3"); + gLocalAccount = createAccount("local"); + MailServices.accounts.defaultAccount = gPopAccount; + + let identity = getSmtpIdentity( + "identity@foo.invalid", + getBasicSmtpServer(gServer.port) + ); + gPopAccount.addIdentity(identity); + gPopAccount.defaultIdentity = identity; + + // Test is using the Sent folder and Outbox folder of the local account. + gLocalRootFolder = gLocalAccount.incomingServer.rootFolder; + gLocalRootFolder.createSubfolder("Sent", null); + gLocalRootFolder.createSubfolder("Templates", null); + gLocalRootFolder.createSubfolder("Fcc", null); + MailServices.accounts.setSpecialFolders(); + + registerCleanupFunction(() => { + gServer.stop(); + }); +}); + +add_task(async function test_no_permission() { + let files = { + "background.js": async () => { + let details = { + to: ["send@test.invalid"], + subject: "Test send", + }; + + // Open a compose window with a message. + let tab = await browser.compose.beginNew(details); + + // Send now. It should fail due to the missing compose.send permission. + await browser.test.assertThrows( + () => browser.compose.saveMessage(tab.id), + /browser.compose.saveMessage is not a function/, + "browser.compose.saveMessage() should reject, if the permission compose.save is not granted." + ); + + // Clean up. + let removedWindowPromise = window.waitForEvent("windows.onRemoved"); + browser.tabs.remove(tab.id); + await removedWindowPromise; + + browser.test.notifyPass("finished"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["compose"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); +}); + +// Helper function to test saving messages. +async function runTest(config) { + let files = { + "background.js": async () => { + let [config] = await window.sendMessage("getConfig"); + + let accounts = await browser.accounts.list(); + browser.test.assertEq(2, accounts.length, "number of accounts"); + let localAccount = accounts.find(a => a.type == "none"); + let fccFolder = localAccount.folders.find(f => f.name == "Fcc"); + browser.test.assertTrue( + !!fccFolder, + "should find the additional fcc folder" + ); + + // Prepare test data. + let allDetails = []; + for (let i = 0; i < 5; i++) { + allDetails.push({ + to: [`test${i}@test.invalid`], + subject: `Test${i} save as ${config.expected.mode}`, + additionalFccFolder: + config.expected.fcc.length > 1 ? fccFolder : null, + }); + } + + // Open multiple compose windows. + for (let details of allDetails) { + details.tab = await browser.compose.beginNew(details); + } + + // Add onAfterSave listener + let collectedEventsMap = new Map(); + function onAfterSaveListener(tab, info) { + collectedEventsMap.set(tab.id, info); + } + browser.compose.onAfterSave.addListener(onAfterSaveListener); + + // Initiate saving of all compose windows at the same time. + let allPromises = []; + for (let details of allDetails) { + allPromises.push( + browser.compose.saveMessage(details.tab.id, config.mode) + ); + } + + // Wait until all messages have been saved. + let allRv = await Promise.all(allPromises); + + for (let i = 0; i < allDetails.length; i++) { + let rv = allRv[i]; + let details = allDetails[i]; + // Find the message with a matching headerMessageId. + + browser.test.assertEq( + config.expected.mode, + rv.mode, + "The mode of the last message operation should be correct." + ); + browser.test.assertEq( + config.expected.fcc.length, + rv.messages.length, + "Should find the correct number of saved messages for this save operation." + ); + + // Check expected FCC folders. + for (let i = 0; i < config.expected.fcc.length; i++) { + // Read the actual messages in the fcc folder. + let savedMessages = await window.sendMessage( + "getMessagesInFolder", + `${config.expected.fcc[i]}` + ); + // Find the currently processed message. + let savedMessage = savedMessages.find( + m => m.messageId == rv.messages[i].headerMessageId + ); + // Compare saved message to original message. + browser.test.assertEq( + details.subject, + savedMessage.subject, + "The subject of the message in the fcc folder should be correct." + ); + + // Check returned details. + browser.test.assertEq( + details.subject, + rv.messages[i].subject, + "The subject of the saved message should be correct." + ); + browser.test.assertEq( + details.to[0], + rv.messages[i].recipients[0], + "The recipients of the saved message should be correct." + ); + browser.test.assertEq( + `/${config.expected.fcc[i]}`, + rv.messages[i].folder.path, + "The saved message should be in the correct folder." + ); + } + + let removedWindowPromise = window.waitForEvent("windows.onRemoved"); + browser.tabs.remove(details.tab.id); + await removedWindowPromise; + } + + // Check onAfterSave listener + browser.compose.onAfterSave.removeListener(onAfterSaveListener); + browser.test.assertEq( + allDetails.length, + collectedEventsMap.size, + "Should have received the correct number of onAfterSave events" + ); + let collectedEvents = [...collectedEventsMap.values()]; + for (let detail of allDetails) { + let msg = collectedEvents.find( + e => e.messages[0].subject == detail.subject + ); + browser.test.assertTrue( + msg, + "Should have received an onAfterSave event for every single message" + ); + } + browser.test.assertEq( + collectedEventsMap.size, + collectedEvents.filter(e => e.mode == config.expected.mode).length, + "All events should have the correct mode." + ); + + // Remove all saved messages. + for (let fcc of config.expected.fcc) { + await window.sendMessage("clearMessagesInFolder", fcc); + } + + browser.test.notifyPass("finished"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["compose", "compose.save", "messagesRead", "accountsRead"], + }, + }); + + extension.onMessage("getConfig", async () => { + extension.sendMessage(config); + }); + + extension.onMessage("getMessagesInFolder", async folderName => { + let folder = gLocalRootFolder.getChildNamed(folderName); + let messages = [...folder.messages].map(m => { + let { subject, messageId, recipients } = m; + return { subject, messageId, recipients }; + }); + extension.sendMessage(...messages); + }); + + extension.onMessage("clearMessagesInFolder", async folderName => { + let folder = gLocalRootFolder.getChildNamed(folderName); + let messages = [...folder.messages]; + await new Promise(resolve => { + folder.deleteMessages( + messages, + null, + true, + false, + { OnStopCopy: resolve }, + false + ); + }); + + Assert.equal(0, [...folder.messages].length, "folder should be empty"); + extension.sendMessage(); + }); + + extension.onMessage("checkWindow", async expected => { + await checkComposeHeaders(expected); + extension.sendMessage(); + }); + + await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); + gServer.resetTest(); +} + +// Test with template save mode. +add_task(async function test_saveAsTemplate() { + await runTest({ + mode: { mode: "template" }, + expected: { + mode: "template", + fcc: ["Templates"], + }, + }); +}); + +// Test with template save mode and additional fcc +add_task(async function test_saveAsTemplate_with_additional_fcc() { + await runTest({ + mode: { mode: "template" }, + expected: { + mode: "template", + fcc: ["Templates", "Fcc"], + }, + }); +}); + +// Test onAfterSave when saving templates for MV3 +add_task(async function test_onAfterSave_MV3_event_pages() { + let files = { + "background.js": async () => { + // Whenever the extension starts or wakes up, hasFired is set to false. In + // case of a wake-up, the first fired event is the one that woke up the background. + let hasFired = false; + + browser.compose.onAfterSave.addListener((tab, saveInfo) => { + // Only send the first event after background wake-up, this should be + // the only one expected. + if (!hasFired) { + hasFired = true; + browser.test.sendMessage("onAfterSave received", saveInfo); + } + }); + + browser.test.sendMessage("background started"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + manifest_version: 3, + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["compose"], + browser_specific_settings: { + gecko: { id: "compose.onAfterSave@xpcshell.test" }, + }, + }, + }); + + function checkPersistentListeners({ primed }) { + // A persistent event is referenced by its moduleName as defined in + // ext-mails.json, not by its actual namespace. + const persistent_events = ["compose.onAfterSave"]; + + for (let event of persistent_events) { + let [moduleName, eventName] = event.split("."); + assertPersistentListeners(extension, moduleName, eventName, { + primed, + }); + } + } + + let composeWindow = await openComposeWindow(gPopAccount); + await focusWindow(composeWindow); + + await extension.startup(); + await extension.awaitMessage("background started"); + // The listeners should be persistent, but not primed. + checkPersistentListeners({ primed: false }); + + // Trigger onAfterSave without terminating the background first. + + composeWindow.SetComposeDetails({ to: "first@invalid.net" }); + composeWindow.SaveAsTemplate(); + let firstSaveInfo = await extension.awaitMessage("onAfterSave received"); + Assert.equal( + "template", + firstSaveInfo.mode, + "Returned SaveInfo should be correct" + ); + + // Terminate background and re-trigger onAfterSave. + + await extension.terminateBackground({ disableResetIdleForTest: true }); + // The listeners should be primed. + checkPersistentListeners({ primed: true }); + + composeWindow.SetComposeDetails({ to: "second@invalid.net" }); + composeWindow.SaveAsTemplate(); + let secondSaveInfo = await extension.awaitMessage("onAfterSave received"); + Assert.equal( + "template", + secondSaveInfo.mode, + "Returned SaveInfo should be correct" + ); + + // The background should have been restarted. + await extension.awaitMessage("background started"); + // The listener should no longer be primed. + checkPersistentListeners({ primed: false }); + + await extension.unload(); + composeWindow.close(); +}); -- cgit v1.2.3