summaryrefslogtreecommitdiffstats
path: root/comm/mail/test/browser/attachment/browser_attachmentMenus.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/test/browser/attachment/browser_attachmentMenus.js')
-rw-r--r--comm/mail/test/browser/attachment/browser_attachmentMenus.js565
1 files changed, 565 insertions, 0 deletions
diff --git a/comm/mail/test/browser/attachment/browser_attachmentMenus.js b/comm/mail/test/browser/attachment/browser_attachmentMenus.js
new file mode 100644
index 0000000000..cc24e6ccc9
--- /dev/null
+++ b/comm/mail/test/browser/attachment/browser_attachmentMenus.js
@@ -0,0 +1,565 @@
+/* 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 folder;
+var messenger;
+var epsilon;
+
+var {
+ create_body_part,
+ create_deleted_attachment,
+ create_detached_attachment,
+ create_enclosure_attachment,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/AttachmentHelpers.jsm"
+);
+var {
+ add_message_to_folder,
+ be_in_folder,
+ close_popup,
+ create_folder,
+ create_message,
+ get_about_message,
+ mc,
+ select_click_row,
+ wait_for_popup_to_open,
+} = ChromeUtils.import(
+ "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
+);
+
+var aboutMessage = get_about_message();
+
+var textAttachment =
+ "Can't make the frug contest, Helen; stomach's upset. I'll fix you, " +
+ "Ubik! Ubik drops you back in the thick of things fast. Taken as " +
+ "directed, Ubik speeds relief to head and stomach. Remember: Ubik is " +
+ "only seconds away. Avoid prolonged use.";
+
+var detachedName = "./attachment.txt";
+var missingName = "./nonexistent.txt";
+var deletedName = "deleted.txt";
+
+// create some messages that have various types of attachments
+var messages = [
+ {
+ name: "regular_attachment",
+ attachments: [{ body: textAttachment, filename: "ubik.txt", format: "" }],
+ menuStates: [{ open: true, save: true, detach: true, delete_: true }],
+ allMenuStates: { open: true, save: true, detach: true, delete_: true },
+ },
+ {
+ name: "detached_attachment",
+ bodyPart: null,
+ menuStates: [{ open: true, save: true, detach: false, delete_: false }],
+ allMenuStates: { open: true, save: true, detach: false, delete_: false },
+ },
+ {
+ name: "detached_attachment_with_missing_file",
+ bodyPart: null,
+ menuStates: [{ open: false, save: false, detach: false, delete_: false }],
+ allMenuStates: { open: false, save: false, detach: false, delete_: false },
+ },
+ {
+ name: "deleted_attachment",
+ bodyPart: null,
+ menuStates: [{ open: false, save: false, detach: false, delete_: false }],
+ allMenuStates: { open: false, save: false, detach: false, delete_: false },
+ },
+ {
+ name: "multiple_attachments",
+ attachments: [
+ { body: textAttachment, filename: "ubik.txt", format: "" },
+ { body: textAttachment, filename: "ubik2.txt", format: "" },
+ ],
+ menuStates: [
+ { open: true, save: true, detach: true, delete_: true },
+ { open: true, save: true, detach: true, delete_: true },
+ ],
+ allMenuStates: { open: true, save: true, detach: true, delete_: true },
+ },
+ {
+ name: "multiple_attachments_one_detached",
+ bodyPart: null,
+ attachments: [{ body: textAttachment, filename: "ubik.txt", format: "" }],
+ menuStates: [
+ { open: true, save: true, detach: false, delete_: false },
+ { open: true, save: true, detach: true, delete_: true },
+ ],
+ allMenuStates: { open: true, save: true, detach: true, delete_: true },
+ },
+ {
+ name: "multiple_attachments_one_detached_with_missing_file",
+ bodyPart: null,
+ attachments: [{ body: textAttachment, filename: "ubik.txt", format: "" }],
+ menuStates: [
+ { open: false, save: false, detach: false, delete_: false },
+ { open: true, save: true, detach: true, delete_: true },
+ ],
+ allMenuStates: { open: true, save: true, detach: true, delete_: true },
+ },
+ {
+ name: "multiple_attachments_one_deleted",
+ bodyPart: null,
+ attachments: [{ body: textAttachment, filename: "ubik.txt", format: "" }],
+ menuStates: [
+ { open: false, save: false, detach: false, delete_: false },
+ { open: true, save: true, detach: true, delete_: true },
+ ],
+ allMenuStates: { open: true, save: true, detach: true, delete_: true },
+ },
+ {
+ name: "multiple_attachments_all_detached",
+ bodyPart: null,
+ menuStates: [
+ { open: true, save: true, detach: false, delete_: false },
+ { open: true, save: true, detach: false, delete_: false },
+ ],
+ allMenuStates: { open: true, save: true, detach: false, delete_: false },
+ },
+ {
+ name: "multiple_attachments_all_detached_with_missing_files",
+ bodyPart: null,
+ menuStates: [
+ { open: false, save: false, detach: false, delete_: false },
+ { open: false, save: false, detach: false, delete_: false },
+ ],
+ allMenuStates: { open: false, save: false, detach: false, delete_: false },
+ },
+ {
+ name: "multiple_attachments_all_deleted",
+ bodyPart: null,
+ menuStates: [
+ { open: false, save: false, detach: false, delete_: false },
+ { open: false, save: false, detach: false, delete_: false },
+ ],
+ allMenuStates: { open: false, save: false, detach: false, delete_: false },
+ },
+ {
+ name: "link_enclosure_valid",
+ bodyPart: null,
+ menuStates: [{ open: true, save: true, detach: false, delete_: false }],
+ allMenuStates: { open: true, save: true, detach: false, delete_: false },
+ },
+ {
+ name: "link_enclosure_invalid",
+ bodyPart: null,
+ menuStates: [{ open: false, save: false, detach: false, delete_: false }],
+ allMenuStates: { open: false, save: false, detach: false, delete_: false },
+ },
+ {
+ name: "link_multiple_enclosures",
+ bodyPart: null,
+ menuStates: [
+ { open: true, save: true, detach: false, delete_: false },
+ { open: true, save: true, detach: false, delete_: false },
+ ],
+ allMenuStates: { open: true, save: true, detach: false, delete_: false },
+ },
+ {
+ name: "link_multiple_enclosures_one_invalid",
+ bodyPart: null,
+ menuStates: [
+ { open: true, save: true, detach: false, delete_: false },
+ { open: false, save: false, detach: false, delete_: false },
+ ],
+ allMenuStates: { open: true, save: true, detach: false, delete_: false },
+ },
+ {
+ name: "link_multiple_enclosures_all_invalid",
+ bodyPart: null,
+ menuStates: [
+ { open: false, save: false, detach: false, delete_: false },
+ { open: false, save: false, detach: false, delete_: false },
+ ],
+ allMenuStates: { open: false, save: false, detach: false, delete_: false },
+ },
+];
+
+add_setup(async function () {
+ messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+ /* Today's gory details (thanks to Jonathan Protzenko): libmime somehow
+ * counts the trailing newline for an attachment MIME part. Most of the time,
+ * assuming attachment has N bytes (no matter what's inside, newlines or
+ * not), libmime will return N + 1 bytes. On Linux and Mac, this always
+ * holds. However, on Windows, if the attachment is not encoded (that is, is
+ * inline text), libmime will return N + 2 bytes.
+ */
+ epsilon = "@mozilla.org/windows-registry-key;1" in Cc ? 2 : 1;
+
+ // set up our detached/deleted attachments
+ var detachedFile = new FileUtils.File(
+ getTestFilePath(`data/${detachedName}`)
+ );
+ var detached = create_body_part("Here is a file", [
+ create_detached_attachment(detachedFile, "text/plain"),
+ ]);
+ var multiple_detached = create_body_part("Here are some files", [
+ create_detached_attachment(detachedFile, "text/plain"),
+ create_detached_attachment(detachedFile, "text/plain"),
+ ]);
+
+ var missingFile = new FileUtils.File(getTestFilePath(`data/${missingName}`));
+ var missing = create_body_part(
+ "Here is a file (but you deleted the external file, you silly oaf!)",
+ [create_detached_attachment(missingFile, "text/plain")]
+ );
+ var multiple_missing = create_body_part(
+ "Here are some files (but you deleted the external files, you silly oaf!)",
+ [
+ create_detached_attachment(missingFile, "text/plain"),
+ create_detached_attachment(missingFile, "text/plain"),
+ ]
+ );
+
+ var deleted = create_body_part("Here is a file that you deleted", [
+ create_deleted_attachment(deletedName, "text/plain"),
+ ]);
+ var multiple_deleted = create_body_part(
+ "Here are some files that you deleted",
+ [
+ create_deleted_attachment(deletedName, "text/plain"),
+ create_deleted_attachment(deletedName, "text/plain"),
+ ]
+ );
+
+ var enclosure_valid_url = create_body_part("My blog has the best enclosure", [
+ create_enclosure_attachment(
+ "purr.mp3",
+ "audio/mpeg",
+ "https://example.com",
+ 12345678
+ ),
+ ]);
+ var enclosure_invalid_url = create_body_part(
+ "My blog has the best enclosure with a dead link",
+ [
+ create_enclosure_attachment(
+ "meow.mp3",
+ "audio/mpeg",
+ "https://example.com/invalid"
+ ),
+ ]
+ );
+ var multiple_enclosures = create_body_part(
+ "My blog has the best 2 cat sound enclosures",
+ [
+ create_enclosure_attachment(
+ "purr.mp3",
+ "audio/mpeg",
+ "https://example.com",
+ 1234567
+ ),
+ create_enclosure_attachment(
+ "meow.mp3",
+ "audio/mpeg",
+ "https://example.com",
+ 987654321
+ ),
+ ]
+ );
+ var multiple_enclosures_one_link_invalid = create_body_part(
+ "My blog has the best 2 cat sound enclosures but one is invalid",
+ [
+ create_enclosure_attachment(
+ "purr.mp3",
+ "audio/mpeg",
+ "https://example.com",
+ 1234567
+ ),
+ create_enclosure_attachment(
+ "meow.mp3",
+ "audio/mpeg",
+ "https://example.com/invalid"
+ ),
+ ]
+ );
+ var multiple_enclosures_all_links_invalid = create_body_part(
+ "My blog has 2 enclosures with 2 bad links",
+ [
+ create_enclosure_attachment(
+ "purr.mp3",
+ "audio/mpeg",
+ "https://example.com/invalid"
+ ),
+ create_enclosure_attachment(
+ "meow.mp3",
+ "audio/mpeg",
+ "https://example.com/invalid"
+ ),
+ ]
+ );
+
+ folder = await create_folder("AttachmentMenusA");
+ for (let i = 0; i < messages.length; i++) {
+ // First, add any missing info to the message object.
+ switch (messages[i].name) {
+ case "detached_attachment":
+ case "multiple_attachments_one_detached":
+ messages[i].bodyPart = detached;
+ break;
+ case "multiple_attachments_all_detached":
+ messages[i].bodyPart = multiple_detached;
+ break;
+ case "detached_attachment_with_missing_file":
+ case "multiple_attachments_one_detached_with_missing_file":
+ messages[i].bodyPart = missing;
+ break;
+ case "multiple_attachments_all_detached_with_missing_files":
+ messages[i].bodyPart = multiple_missing;
+ break;
+ case "deleted_attachment":
+ case "multiple_attachments_one_deleted":
+ messages[i].bodyPart = deleted;
+ break;
+ case "multiple_attachments_all_deleted":
+ messages[i].bodyPart = multiple_deleted;
+ break;
+ case "link_enclosure_valid":
+ messages[i].bodyPart = enclosure_valid_url;
+ break;
+ case "link_enclosure_invalid":
+ messages[i].bodyPart = enclosure_invalid_url;
+ break;
+ case "link_multiple_enclosures":
+ messages[i].bodyPart = multiple_enclosures;
+ break;
+ case "link_multiple_enclosures_one_invalid":
+ messages[i].bodyPart = multiple_enclosures_one_link_invalid;
+ break;
+ case "link_multiple_enclosures_all_invalid":
+ messages[i].bodyPart = multiple_enclosures_all_links_invalid;
+ break;
+ }
+
+ await add_message_to_folder([folder], create_message(messages[i]));
+ }
+});
+
+/**
+ * Ensure that the specified element is visible/hidden
+ *
+ * @param id the id of the element to check
+ * @param visible true if the element should be visible, false otherwise
+ */
+function assert_shown(id, visible) {
+ Assert.notEqual(
+ aboutMessage.document.getElementById(id).hidden,
+ visible,
+ `"${id}" should be ${visible ? "visible" : "hidden"}`
+ );
+}
+
+/**
+ * Ensure that the specified element is enabled/disabled
+ *
+ * @param id the id of the element to check
+ * @param enabled true if the element should be enabled, false otherwise
+ */
+function assert_enabled(id, enabled) {
+ Assert.notEqual(
+ aboutMessage.document.getElementById(id).disabled,
+ enabled,
+ `"${id}" should be ${enabled ? "enabled" : "disabled"}`
+ );
+}
+
+/**
+ * Check that the menu states in the "save" toolbar button are correct.
+ *
+ * @param expected a dictionary containing the expected states
+ */
+async function check_toolbar_menu_states_single(expected) {
+ assert_shown("attachmentSaveAllSingle", true);
+ assert_shown("attachmentSaveAllMultiple", false);
+
+ if (expected.save === false) {
+ assert_enabled("attachmentSaveAllSingle", false);
+ } else {
+ assert_enabled("attachmentSaveAllSingle", true);
+ let dm = aboutMessage.document.querySelector(
+ "#attachmentSaveAllSingle .toolbarbutton-menubutton-dropmarker"
+ );
+ EventUtils.synthesizeMouseAtCenter(dm, { clickCount: 1 }, aboutMessage);
+ await wait_for_popup_to_open(
+ aboutMessage.document.getElementById("attachmentSaveAllSingleMenu")
+ );
+
+ try {
+ assert_enabled("button-openAttachment", expected.open);
+ assert_enabled("button-saveAttachment", expected.save);
+ assert_enabled("button-detachAttachment", expected.detach);
+ assert_enabled("button-deleteAttachment", expected.delete_);
+ } finally {
+ await close_popup(
+ aboutMessage,
+ aboutMessage.document.getElementById("attachmentSaveAllSingleMenu")
+ );
+ }
+ }
+}
+
+/**
+ * Check that the menu states in the "save all" toolbar button are correct.
+ *
+ * @param expected a dictionary containing the expected states
+ */
+async function check_toolbar_menu_states_multiple(expected) {
+ assert_shown("attachmentSaveAllSingle", false);
+ assert_shown("attachmentSaveAllMultiple", true);
+
+ if (expected.save === false) {
+ assert_enabled("attachmentSaveAllMultiple", false);
+ } else {
+ assert_enabled("attachmentSaveAllMultiple", true);
+ let dm = aboutMessage.document.querySelector(
+ "#attachmentSaveAllMultiple .toolbarbutton-menubutton-dropmarker"
+ );
+ EventUtils.synthesizeMouseAtCenter(dm, { clickCount: 1 }, aboutMessage);
+ await wait_for_popup_to_open(
+ aboutMessage.document.getElementById("attachmentSaveAllMultipleMenu")
+ );
+
+ try {
+ assert_enabled("button-openAllAttachments", expected.open);
+ assert_enabled("button-saveAllAttachments", expected.save);
+ assert_enabled("button-detachAllAttachments", expected.detach);
+ assert_enabled("button-deleteAllAttachments", expected.delete_);
+ } finally {
+ await close_popup(
+ mc,
+ aboutMessage.document.getElementById("attachmentSaveAllMultipleMenu")
+ );
+ }
+ }
+}
+
+/**
+ * Check that the menu states in the single item context menu are correct
+ *
+ * @param expected a dictionary containing the expected states
+ */
+async function check_menu_states_single(index, expected) {
+ let attachmentList = aboutMessage.document.getElementById("attachmentList");
+ let node = attachmentList.getItemAtIndex(index);
+
+ let contextMenu = aboutMessage.document.getElementById(
+ "attachmentItemContext"
+ );
+ let shownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ attachmentList.selectItem(node);
+ EventUtils.synthesizeMouseAtCenter(
+ node,
+ { type: "contextmenu" },
+ aboutMessage
+ );
+ await shownPromise;
+
+ try {
+ assert_shown("context-openAttachment", true);
+ assert_shown("context-saveAttachment", true);
+ assert_shown("context-menu-separator", true);
+ assert_shown("context-detachAttachment", true);
+ assert_shown("context-deleteAttachment", true);
+
+ assert_enabled("context-openAttachment", expected.open);
+ assert_enabled("context-saveAttachment", expected.save);
+ assert_enabled("context-detachAttachment", expected.detach);
+ assert_enabled("context-deleteAttachment", expected.delete_);
+ } finally {
+ let hiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await hiddenPromise;
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+}
+
+/**
+ * Check that the menu states in the all items context menu are correct
+ *
+ * @param expected a dictionary containing the expected states
+ */
+async function check_menu_states_all(expected) {
+ // Using a rightClick here is unsafe, because we need to hit the empty area
+ // beside the attachment items and that seems to be different per platform.
+ // Using DOM methods to open the popup works fine.
+ let contextMenu = aboutMessage.document.getElementById(
+ "attachmentListContext"
+ );
+ let shownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
+ aboutMessage.document
+ .getElementById("attachmentListContext")
+ .openPopup(aboutMessage.document.getElementById("attachmentList"));
+ await shownPromise;
+
+ try {
+ assert_shown("context-openAllAttachments", true);
+ assert_shown("context-saveAllAttachments", true);
+ assert_shown("context-menu-separator-all", true);
+ assert_shown("context-detachAllAttachments", true);
+ assert_shown("context-deleteAllAttachments", true);
+
+ assert_enabled("context-openAllAttachments", expected.open);
+ assert_enabled("context-saveAllAttachments", expected.save);
+ assert_enabled("context-detachAllAttachments", expected.detach);
+ assert_enabled("context-deleteAllAttachments", expected.delete_);
+ } finally {
+ await close_popup(
+ aboutMessage,
+ aboutMessage.document.getElementById("attachmentListContext")
+ );
+ }
+}
+
+async function help_test_attachment_menus(index) {
+ await be_in_folder(folder);
+ select_click_row(index);
+ let expectedStates = messages[index].menuStates;
+
+ let aboutMessage = get_about_message();
+ aboutMessage.toggleAttachmentList(true);
+
+ for (let attachment of aboutMessage.currentAttachments) {
+ // Ensure all attachments are resolved; other than external they already
+ // should be.
+ await attachment.isEmpty();
+ }
+
+ if (expectedStates.length == 1) {
+ await check_toolbar_menu_states_single(messages[index].allMenuStates);
+ } else {
+ await check_toolbar_menu_states_multiple(messages[index].allMenuStates);
+ }
+
+ await check_menu_states_all(messages[index].allMenuStates);
+ for (let i = 0; i < expectedStates.length; i++) {
+ await check_menu_states_single(i, expectedStates[i]);
+ }
+}
+
+// Generate a test for each message in |messages|.
+for (let i = 0; i < messages.length; i++) {
+ add_task(function () {
+ return help_test_attachment_menus(i);
+ });
+}
+
+add_task(() => {
+ Assert.report(
+ false,
+ undefined,
+ undefined,
+ "Test ran to completion successfully"
+ );
+});
+
+registerCleanupFunction(() => {
+ // Remove created folders.
+ folder.deleteSelf(null);
+});