diff options
Diffstat (limited to 'comm/mail/components/extensions/test/xpcshell/test_ext_messages_update.js')
-rw-r--r-- | comm/mail/components/extensions/test/xpcshell/test_ext_messages_update.js | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/comm/mail/components/extensions/test/xpcshell/test_ext_messages_update.js b/comm/mail/components/extensions/test/xpcshell/test_ext_messages_update.js new file mode 100644 index 0000000000..4771f3ee17 --- /dev/null +++ b/comm/mail/components/extensions/test/xpcshell/test_ext_messages_update.js @@ -0,0 +1,415 @@ +/* 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"; + +var { ExtensionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" +); +var { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); +var { ExtensionsUI } = ChromeUtils.import( + "resource:///modules/ExtensionsUI.jsm" +); +var { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +ExtensionTestUtils.mockAppInfo(); +AddonTestUtils.maybeInit(this); + +registerCleanupFunction(async () => { + // Remove the temporary MozillaMailnews folder, which is not deleted in time when + // the cleanupFunction registered by AddonTestUtils.maybeInit() checks for left over + // files in the temp folder. + // Note: PathUtils.tempDir points to the system temp folder, which is different. + let path = PathUtils.join( + Services.dirsvc.get("TmpD", Ci.nsIFile).path, + "MozillaMailnews" + ); + await IOUtils.remove(path, { recursive: true }); +}); + +// Function to start an event page extension (MV3), which can be called whenever +// the main test is about to trigger an event. The extension terminates its +// background and listens for that single event, verifying it is waking up correctly. +async function event_page_extension(eventName, actionCallback) { + let ext = ExtensionTestUtils.loadExtension({ + 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; + let _eventName = browser.runtime.getManifest().description; + + browser.messages[_eventName].addListener(async (...args) => { + // Only send the first event after background wake-up, this should + // be the only one expected. + if (!hasFired) { + hasFired = true; + browser.test.sendMessage(`${_eventName} received`, args); + } + }); + browser.test.sendMessage("background started"); + }, + }, + manifest: { + manifest_version: 3, + description: eventName, + background: { scripts: ["background.js"] }, + browser_specific_settings: { + gecko: { id: "event_page_extension@mochi.test" }, + }, + permissions: ["accountsRead", "messagesRead", "messagesMove"], + }, + }); + await ext.startup(); + await ext.awaitMessage("background started"); + // The listener should be persistent, but not primed. + assertPersistentListeners(ext, "messages", eventName, { primed: false }); + + await ext.terminateBackground({ disableResetIdleForTest: true }); + // Verify the primed persistent listener. + assertPersistentListeners(ext, "messages", eventName, { primed: true }); + + await actionCallback(); + let rv = await ext.awaitMessage(`${eventName} received`); + await ext.awaitMessage("background started"); + // The listener should be persistent, but not primed. + assertPersistentListeners(ext, "messages", eventName, { primed: false }); + + await ext.unload(); + return rv; +} + +add_task( + { + skip_if: () => IS_NNTP, + }, + async function test_update() { + await AddonTestUtils.promiseStartupManager(); + + let account = createAccount(); + let rootFolder = account.incomingServer.rootFolder; + let testFolder0 = await createSubfolder(rootFolder, "test0"); + await createMessages(testFolder0, 1); + testFolder0.addKeywordsToMessages( + [[...testFolder0.messages][0]], + "testkeyword" + ); + + let files = { + "background.js": async () => { + async function capturePrimedEvent(eventName, callback) { + let eventPageExtensionReadyPromise = window.waitForMessage(); + browser.test.sendMessage("capturePrimedEvent", eventName); + await eventPageExtensionReadyPromise; + let eventPageExtensionFinishedPromise = window.waitForMessage(); + callback(); + return eventPageExtensionFinishedPromise; + } + + function newUpdatePromise(numberOfEventsToCollapse = 1) { + return new Promise(resolve => { + let seenEvents = {}; + const listener = (msg, props) => { + if (!seenEvents.hasOwnProperty(msg.id)) { + seenEvents[msg.id] = { + counts: 0, + props: {}, + }; + } + + seenEvents[msg.id].counts++; + for (let prop of Object.keys(props)) { + seenEvents[msg.id].props[prop] = props[prop]; + } + + if (seenEvents[msg.id].counts == numberOfEventsToCollapse) { + browser.messages.onUpdated.removeListener(listener); + resolve({ msg, props: seenEvents[msg.id].props }); + } + }; + browser.messages.onUpdated.addListener(listener); + }); + } + let tags = await browser.messages.listTags(); + let [data] = await window.sendMessage("getFolder"); + let messageList = await browser.messages.list(data.folder); + browser.test.assertEq(1, messageList.messages.length); + let message = messageList.messages[0]; + browser.test.assertFalse(message.flagged); + browser.test.assertFalse(message.read); + browser.test.assertFalse(message.junk); + browser.test.assertEq(0, message.junkScore); + browser.test.assertEq(0, message.tags.length); + browser.test.assertEq(data.size, message.size); + browser.test.assertEq("0@made.up.invalid", message.headerMessageId); + + // Test that setting flagged works. + let updatePromise = newUpdatePromise(); + let primedUpdatedInfo = await capturePrimedEvent("onUpdated", () => + browser.messages.update(message.id, { flagged: true }) + ); + let updateInfo = await updatePromise; + window.assertDeepEqual( + [updateInfo.msg, updateInfo.props], + primedUpdatedInfo, + "The primed and non-primed onUpdated events should return the same values", + { strict: true } + ); + browser.test.assertEq(message.id, updateInfo.msg.id); + window.assertDeepEqual({ flagged: true }, updateInfo.props); + await window.sendMessage("flagged"); + + // Test that setting read works. + updatePromise = newUpdatePromise(); + primedUpdatedInfo = await capturePrimedEvent("onUpdated", () => + browser.messages.update(message.id, { read: true }) + ); + updateInfo = await updatePromise; + window.assertDeepEqual( + [updateInfo.msg, updateInfo.props], + primedUpdatedInfo, + "The primed and non-primed onUpdated events should return the same values", + { strict: true } + ); + browser.test.assertEq(message.id, updateInfo.msg.id); + window.assertDeepEqual({ read: true }, updateInfo.props); + await window.sendMessage("read"); + + // Test that setting junk works. + updatePromise = newUpdatePromise(); + primedUpdatedInfo = await capturePrimedEvent("onUpdated", () => + browser.messages.update(message.id, { junk: true }) + ); + updateInfo = await updatePromise; + window.assertDeepEqual( + [updateInfo.msg, updateInfo.props], + primedUpdatedInfo, + "The primed and non-primed onUpdated events should return the same values", + { strict: true } + ); + browser.test.assertEq(message.id, updateInfo.msg.id); + window.assertDeepEqual({ junk: true }, updateInfo.props); + await window.sendMessage("junk"); + + // Test that setting one tag works. + updatePromise = newUpdatePromise(); + primedUpdatedInfo = await capturePrimedEvent("onUpdated", () => + browser.messages.update(message.id, { tags: [tags[0].key] }) + ); + updateInfo = await updatePromise; + window.assertDeepEqual( + [updateInfo.msg, updateInfo.props], + primedUpdatedInfo, + "The primed and non-primed onUpdated events should return the same values", + { strict: true } + ); + browser.test.assertEq(message.id, updateInfo.msg.id); + window.assertDeepEqual({ tags: [tags[0].key] }, updateInfo.props); + await window.sendMessage("tags1"); + + // Test that setting two tags works. We get 3 events: one removing tags0, + // one adding tags1 and one adding tags2. updatePromise is waiting for + // the third one before resolving. + updatePromise = newUpdatePromise(3); + await browser.messages.update(message.id, { + tags: [tags[1].key, tags[2].key], + }); + updateInfo = await updatePromise; + browser.test.assertEq(message.id, updateInfo.msg.id); + window.assertDeepEqual( + { tags: [tags[1].key, tags[2].key] }, + updateInfo.props + ); + await window.sendMessage("tags2"); + + // Test that unspecified properties aren't changed. + let listenerCalls = 0; + const listenerFunc = (msg, props) => { + listenerCalls++; + }; + browser.messages.onUpdated.addListener(listenerFunc); + await browser.messages.update(message.id, {}); + await window.sendMessage("empty"); + // Check if the no-op update call triggered a listener. + await new Promise(resolve => setTimeout(resolve)); + browser.messages.onUpdated.removeListener(listenerFunc); + browser.test.assertEq( + 0, + listenerCalls, + "Not expecting listener callbacks on no-op updates." + ); + + message = await browser.messages.get(message.id); + browser.test.assertTrue(message.flagged); + browser.test.assertTrue(message.read); + browser.test.assertTrue(message.junk); + browser.test.assertEq(100, message.junkScore); + browser.test.assertEq(2, message.tags.length); + browser.test.assertEq(tags[1].key, message.tags[0]); + browser.test.assertEq(tags[2].key, message.tags[1]); + browser.test.assertEq("0@made.up.invalid", message.headerMessageId); + + // Test that clearing properties works. + updatePromise = newUpdatePromise(5); + await browser.messages.update(message.id, { + flagged: false, + read: false, + junk: false, + tags: [], + }); + updateInfo = await updatePromise; + window.assertDeepEqual( + { + flagged: false, + read: false, + junk: false, + tags: [], + }, + updateInfo.props + ); + await window.sendMessage("clear"); + + message = await browser.messages.get(message.id); + browser.test.assertFalse(message.flagged); + browser.test.assertFalse(message.read); + browser.test.assertFalse(message.external); + browser.test.assertFalse(message.junk); + browser.test.assertEq(0, message.junkScore); + browser.test.assertEq(0, message.tags.length); + browser.test.assertEq("0@made.up.invalid", message.headerMessageId); + + browser.test.notifyPass("finished"); + }, + "utils.js": await getUtilsJS(), + }; + let extension = ExtensionTestUtils.loadExtension({ + files, + manifest: { + background: { scripts: ["utils.js", "background.js"] }, + permissions: ["accountsRead", "messagesRead"], + browser_specific_settings: { + gecko: { id: "messages.update@mochi.test" }, + }, + }, + }); + + let message = [...testFolder0.messages][0]; + ok(!message.isFlagged); + ok(!message.isRead); + equal(message.getStringProperty("keywords"), "testkeyword"); + + extension.onMessage("capturePrimedEvent", async eventName => { + let primedEventData = await event_page_extension(eventName, () => { + // Resume execution in the main test, after the event page extension is + // ready to capture the event with deactivated background. + extension.sendMessage(); + }); + extension.sendMessage(...primedEventData); + }); + + extension.onMessage("flagged", async () => { + await TestUtils.waitForCondition(() => message.isFlagged); + extension.sendMessage(); + }); + + extension.onMessage("read", async () => { + await TestUtils.waitForCondition(() => message.isRead); + extension.sendMessage(); + }); + + extension.onMessage("junk", async () => { + await TestUtils.waitForCondition( + () => message.getStringProperty("junkscore") == 100 + ); + extension.sendMessage(); + }); + + extension.onMessage("tags1", async () => { + if (IS_IMAP) { + // Only IMAP sets the junk/nonjunk keyword. + await TestUtils.waitForCondition( + () => + message.getStringProperty("keywords") == "testkeyword junk $label1" + ); + } else { + await TestUtils.waitForCondition( + () => message.getStringProperty("keywords") == "testkeyword $label1" + ); + } + extension.sendMessage(); + }); + + extension.onMessage("tags2", async () => { + if (IS_IMAP) { + await TestUtils.waitForCondition( + () => + message.getStringProperty("keywords") == + "testkeyword junk $label2 $label3" + ); + } else { + await TestUtils.waitForCondition( + () => + message.getStringProperty("keywords") == + "testkeyword $label2 $label3" + ); + } + extension.sendMessage(); + }); + + extension.onMessage("empty", async () => { + await TestUtils.waitForCondition(() => message.isFlagged); + await TestUtils.waitForCondition(() => message.isRead); + if (IS_IMAP) { + await TestUtils.waitForCondition( + () => + message.getStringProperty("keywords") == + "testkeyword junk $label2 $label3" + ); + } else { + await TestUtils.waitForCondition( + () => + message.getStringProperty("keywords") == + "testkeyword $label2 $label3" + ); + } + extension.sendMessage(); + }); + + extension.onMessage("clear", async () => { + await TestUtils.waitForCondition(() => !message.isFlagged); + await TestUtils.waitForCondition(() => !message.isRead); + await TestUtils.waitForCondition( + () => message.getStringProperty("junkscore") == 0 + ); + if (IS_IMAP) { + await TestUtils.waitForCondition( + () => message.getStringProperty("keywords") == "testkeyword nonjunk" + ); + } else { + await TestUtils.waitForCondition( + () => message.getStringProperty("keywords") == "testkeyword" + ); + } + extension.sendMessage(); + }); + + extension.onMessage("getFolder", async () => { + extension.sendMessage({ + folder: { accountId: account.key, path: "/test0" }, + size: message.messageSize, + }); + }); + + await extension.startup(); + await extension.awaitFinish("finished"); + await extension.unload(); + + cleanUpAccount(account); + await AddonTestUtils.promiseShutdownManager(); + } +); |