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 --- comm/calendar/test/browser/invitations/browser.ini | 31 + .../invitations/browser_attachedPublishEvent.js | 72 ++ .../browser/invitations/browser_icsAttachment.js | 71 ++ .../browser/invitations/browser_identityPrompt.js | 144 ++++ .../test/browser/invitations/browser_imipBar.js | 199 +++++ .../browser/invitations/browser_imipBarCancel.js | 129 +++ .../browser/invitations/browser_imipBarEmail.js | 168 ++++ .../invitations/browser_imipBarExceptionCancel.js | 137 +++ .../invitations/browser_imipBarExceptionOnly.js | 262 ++++++ .../invitations/browser_imipBarExceptions.js | 288 +++++++ .../browser/invitations/browser_imipBarRepeat.js | 218 +++++ .../invitations/browser_imipBarRepeatCancel.js | 186 ++++ .../invitations/browser_imipBarRepeatUpdates.js | 247 ++++++ .../browser/invitations/browser_imipBarUpdates.js | 223 +++++ .../invitations/browser_invitationDisplayNew.js | 257 ++++++ .../browser/invitations/browser_unsupportedFreq.js | 107 +++ .../invitations/data/cancel-repeat-event.eml | 49 ++ .../invitations/data/cancel-single-event.eml | 78 ++ .../browser/invitations/data/exception-major.eml | 49 ++ .../browser/invitations/data/exception-minor.eml | 49 ++ .../invitations/data/meet-meeting-invite.eml | 384 +++++++++ .../invitations/data/message-containing-event.eml | 44 + .../invitations/data/message-non-invite.eml | 115 +++ .../invitations/data/outlook-test-invite.eml | 102 +++ .../test/browser/invitations/data/repeat-event.eml | 49 ++ .../invitations/data/repeat-update-major.eml | 49 ++ .../invitations/data/repeat-update-minor.eml | 49 ++ .../test/browser/invitations/data/single-event.eml | 78 ++ .../invitations/data/teams-meeting-invite.eml | 167 ++++ .../test/browser/invitations/data/update-major.eml | 78 ++ .../test/browser/invitations/data/update-minor.eml | 78 ++ comm/calendar/test/browser/invitations/head.js | 942 +++++++++++++++++++++ 32 files changed, 5099 insertions(+) create mode 100644 comm/calendar/test/browser/invitations/browser.ini create mode 100644 comm/calendar/test/browser/invitations/browser_attachedPublishEvent.js create mode 100644 comm/calendar/test/browser/invitations/browser_icsAttachment.js create mode 100644 comm/calendar/test/browser/invitations/browser_identityPrompt.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBar.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarCancel.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarEmail.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarExceptionCancel.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarExceptionOnly.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarExceptions.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarRepeat.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarRepeatCancel.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarRepeatUpdates.js create mode 100644 comm/calendar/test/browser/invitations/browser_imipBarUpdates.js create mode 100644 comm/calendar/test/browser/invitations/browser_invitationDisplayNew.js create mode 100644 comm/calendar/test/browser/invitations/browser_unsupportedFreq.js create mode 100644 comm/calendar/test/browser/invitations/data/cancel-repeat-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/cancel-single-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/exception-major.eml create mode 100644 comm/calendar/test/browser/invitations/data/exception-minor.eml create mode 100644 comm/calendar/test/browser/invitations/data/meet-meeting-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/message-containing-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/message-non-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/outlook-test-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/repeat-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/repeat-update-major.eml create mode 100644 comm/calendar/test/browser/invitations/data/repeat-update-minor.eml create mode 100644 comm/calendar/test/browser/invitations/data/single-event.eml create mode 100644 comm/calendar/test/browser/invitations/data/teams-meeting-invite.eml create mode 100644 comm/calendar/test/browser/invitations/data/update-major.eml create mode 100644 comm/calendar/test/browser/invitations/data/update-minor.eml create mode 100644 comm/calendar/test/browser/invitations/head.js (limited to 'comm/calendar/test/browser/invitations') diff --git a/comm/calendar/test/browser/invitations/browser.ini b/comm/calendar/test/browser/invitations/browser.ini new file mode 100644 index 0000000000..7c7aa6af46 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser.ini @@ -0,0 +1,31 @@ +[default] +head = ../head.js head.js +prefs = + calendar.item.promptDelete=false + calendar.timezone.local=UTC + calendar.timezone.useSystemTimezone=false + calendar.week.start=0 + 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 +support-files = data/** + +[browser_attachedPublishEvent.js] +[browser_icsAttachment.js] +skip-if = os == 'win' +[browser_identityPrompt.js] +[browser_imipBar.js] +[browser_imipBarCancel.js] +[browser_imipBarEmail.js] +[browser_imipBarExceptionCancel.js] +[browser_imipBarExceptionOnly.js] +[browser_imipBarExceptions.js] +[browser_imipBarRepeat.js] +[browser_imipBarRepeatCancel.js] +[browser_imipBarRepeatUpdates.js] +[browser_imipBarUpdates.js] +[browser_invitationDisplayNew.js] +[browser_unsupportedFreq.js] diff --git a/comm/calendar/test/browser/invitations/browser_attachedPublishEvent.js b/comm/calendar/test/browser/invitations/browser_attachedPublishEvent.js new file mode 100644 index 0000000000..af121a8032 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_attachedPublishEvent.js @@ -0,0 +1,72 @@ +/* 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/. */ + +/** + * Test that attached events - NOT invites - works properly. + * These are attached VCALENDARs that have METHOD:PUBLISH. + */ +"use strict"; + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var gCalendar; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + let receiverAcct = MailServices.accounts.createAccount(); + receiverAcct.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + let receiverIdentity = MailServices.accounts.createIdentity(); + receiverIdentity.email = "john.doe@example.com"; + receiverAcct.addIdentity(receiverIdentity); + gCalendar = CalendarTestUtils.createCalendar("EventTestCal"); + + registerCleanupFunction(() => { + CalendarTestUtils.removeCalendar(gCalendar); + MailServices.accounts.removeAccount(receiverAcct, true); + }); +}); + +/** + * Test that opening a message containing an event with iTIP method "PUBLISH" + * shows the correct UI. + * The party crashing dialog should not show. + */ +add_task(async function test_event_from_eml() { + let file = new FileUtils.File(getTestFilePath("data/message-non-invite.eml")); + + let win = await openMessageFromFile(file); + let aboutMessage = win.document.getElementById("messageBrowser").contentWindow; + let imipBar = aboutMessage.document.getElementById("imip-bar"); + + await TestUtils.waitForCondition(() => !imipBar.collapsed); + info("Ok, iMIP bar is showing"); + + let imipAddButton = aboutMessage.document.getElementById("imipAddButton"); + Assert.ok(!imipAddButton.hidden, "Add button should show"); + + EventUtils.synthesizeMouseAtCenter(imipAddButton, {}, aboutMessage); + + // Make sure the event got added, without showing the party crashing dialog. + await TestUtils.waitForCondition(async () => { + let event = await gCalendar.getItem("1e5fd4e6-bc52-439c-ac76-40da54f57c77@secure.example.com"); + return event; + }); + + await TestUtils.waitForCondition(() => imipAddButton.hidden, "Add button should hide"); + + let imipDetailsButton = aboutMessage.document.getElementById("imipDetailsButton"); + Assert.ok(!imipDetailsButton.hidden, "Details button should show"); + + await BrowserTestUtils.closeWindow(win); +}); diff --git a/comm/calendar/test/browser/invitations/browser_icsAttachment.js b/comm/calendar/test/browser/invitations/browser_icsAttachment.js new file mode 100644 index 0000000000..11bde9144d --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_icsAttachment.js @@ -0,0 +1,71 @@ +/* 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/. */ + +/** + * Test TB can be set as default calendar app. + */ + +/** + * Set TB as default calendar app. + */ +add_setup(function () { + let shellSvc = Cc["@mozilla.org/mail/shell-service;1"].getService(Ci.nsIShellService); + shellSvc.setDefaultClient(false, shellSvc.CALENDAR); + ok(shellSvc.isDefaultClient(false, shellSvc.CALENDAR), "setDefaultClient works"); +}); + +/** + * Test when opening an ics attachment, TB should be shown as an option. + */ +add_task(async function test_ics_attachment() { + let file = new FileUtils.File(getTestFilePath("data/message-containing-event.eml")); + let msgWindow = await openMessageFromFile(file); + let aboutMessage = msgWindow.document.getElementById("messageBrowser").contentWindow; + let promise = BrowserTestUtils.promiseAlertDialog( + null, + "chrome://mozapps/content/downloads/unknownContentType.xhtml", + { + async callback(dialogWindow) { + ok(true, "unknownContentType dialog opened"); + let dialogElement = dialogWindow.document.querySelector("dialog"); + let acceptButton = dialogElement.getButton("accept"); + return new Promise(resolve => { + let observer = new MutationObserver(mutationList => { + mutationList.forEach(async mutation => { + if (mutation.attributeName == "disabled" && !acceptButton.disabled) { + is(acceptButton.disabled, false, "Accept button enabled"); + if (AppConstants.platform != "macosx") { + let bundle = Services.strings.createBundle( + "chrome://branding/locale/brand.properties" + ); + let name = bundle.GetStringFromName("brandShortName"); + // macOS requires extra step in Finder to set TB as default calendar app. + ok( + dialogWindow.document.getElementById("openHandler").label.includes(name), + `${name} is the default calendar app` + ); + } + + // Should really click acceptButton and test + // calender-ics-file-dialog is opened. But on local, a new TB + // instance is started and this test will fail. + dialogElement.getButton("cancel").click(); + resolve(); + } + }); + }); + observer.observe(acceptButton, { attributes: true }); + }); + }, + } + ); + EventUtils.synthesizeMouseAtCenter( + aboutMessage.document.getElementById("attachmentName"), + {}, + aboutMessage + ); + await promise; + + await BrowserTestUtils.closeWindow(msgWindow); +}); diff --git a/comm/calendar/test/browser/invitations/browser_identityPrompt.js b/comm/calendar/test/browser/invitations/browser_identityPrompt.js new file mode 100644 index 0000000000..e2d6fe3115 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_identityPrompt.js @@ -0,0 +1,144 @@ +/* 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/. */ + +/** + * Tests for the calender-itip-identity dialog. + */ + +"use strict"; + +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +var { PromiseTestUtils } = ChromeUtils.import( + "resource://testing-common/mailnews/PromiseTestUtils.jsm" +); +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let receiverAcct; +let receiverIdentity; +let gInbox; +let calendar; + +registerCleanupFunction(() => { + CalendarTestUtils.removeCalendar(calendar); + MailServices.accounts.removeIncomingServer(receiverAcct.incomingServer, true); + MailServices.accounts.removeAccount(receiverAcct); +}); + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + if (MailServices.accounts.accounts.length == 0) { + MailServices.accounts.createLocalMailAccount(); + } + + let rootFolder = MailServices.accounts.localFoldersServer.rootFolder; + if (!rootFolder.containsChildNamed("Inbox")) { + rootFolder.createSubfolder("Inbox", null); + } + gInbox = rootFolder.getChildNamed("Inbox"); + + receiverAcct = MailServices.accounts.createAccount(); + receiverAcct.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + receiverIdentity = MailServices.accounts.createIdentity(); + receiverIdentity.email = "receiver@example.com"; + receiverAcct.addIdentity(receiverIdentity); + + calendar = CalendarTestUtils.createCalendar("Test"); + + let copyListener = new PromiseTestUtils.PromiseCopyListener(); + MailServices.copy.copyFileMessage( + new FileUtils.File(getTestFilePath("data/meet-meeting-invite.eml")), + gInbox, + null, + false, + 0, + "", + copyListener, + null + ); + await copyListener.promise; +}); + +/** + * Tests that the identity prompt shows when accepting an invitation to an + * event with an identity no calendar is configured to use. + */ +add_task(async function testInvitationIdentityPrompt() { + let tabmail = document.getElementById("tabmail"); + let about3Pane = tabmail.currentAbout3Pane; + about3Pane.displayFolder(gInbox.URI); + about3Pane.threadTree.selectedIndex = 0; + + let dialogPromise = BrowserTestUtils.promiseAlertDialog( + null, + "chrome://calendar/content/calendar-itip-identity-dialog.xhtml", + { + async callback(win) { + // Select the identity we want to use. + let menulist = win.document.getElementById("identity-menu"); + for (let i = 0; i < menulist.itemCount; i++) { + let target = menulist.getItemAtIndex(i); + if (target.value == receiverIdentity.fullAddress) { + menulist.selectedIndex = i; + } + } + + win.document.querySelector("dialog").getButton("accept").click(); + }, + } + ); + + // Override this function to intercept the attempt to send the email out. + let sendItemsArgs = []; + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => ({ + scheme: "mailto", + type: "email", + sendItems(receipientArray, item, sender) { + sendItemsArgs = [receipientArray, item, sender]; + return true; + }, + }); + + let aboutMessage = tabmail.currentAboutMessage; + let acceptButton = aboutMessage.document.getElementById("imipAcceptButton"); + await TestUtils.waitForCondition( + () => BrowserTestUtils.is_visible(acceptButton), + "waiting for accept button to become visible" + ); + EventUtils.synthesizeMouseAtCenter(acceptButton, {}, aboutMessage); + await dialogPromise; + + let event; + await TestUtils.waitForCondition(async () => { + event = await calendar.getItem("65m17hsdolmotv3kvmrtg40ont@google.com"); + return event && sendItemsArgs.length; + }); + + // Restore this function. + cal.itip.getImipTransport = getImipTransport; + + let id = `mailto:${receiverIdentity.email}`; + Assert.ok(event, "event was added to the calendar successfully"); + Assert.ok(event.getAttendeeById(id), "selected identity was added to the attendee list"); + Assert.equal( + event.getProperty("X-MOZ-INVITED-ATTENDEE"), + id, + "X-MOZ-INVITED-ATTENDEE is set to the selected identity" + ); + + let [recipientArray, , sender] = sendItemsArgs; + Assert.equal(recipientArray.length, 1, "one recipient for the reply"); + Assert.equal(recipientArray[0].id, "mailto:example@gmail.com", "recipient is event organizer"); + Assert.equal(sender.id, id, "sender is the identity selected"); +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBar.js b/comm/calendar/test/browser/invitations/browser_imipBar.js new file mode 100644 index 0000000000..c9a21a6d2b --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBar.js @@ -0,0 +1,199 @@ +/* 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/. */ + +/** + * Tests for receiving event invitations via the imip-bar. + */ +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { CalItipDefaultEmailTransport } = ChromeUtils.import( + "resource:///modules/CalItipEmailTransport.jsm" +); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests accepting an invitation and sending a response. + */ +add_task(async function testAcceptWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + await clickAction(win, "imipAcceptButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "ACCEPTED", + }, + event + ); + + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting an invitation and sending a response. + */ +add_task(async function testTentativeWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + await clickAction(win, "imipTentativeButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "TENTATIVE", + }, + event + ); + + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining an invitation and sending a response. + */ +add_task(async function testDeclineWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + await clickAction(win, "imipDeclineButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "DECLINED", + }, + event + ); + + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests accepting an invitation without sending a response. + */ +add_task(async function testAcceptWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + await clickMenuAction(win, "imipAcceptButton", "imipAcceptButton_AcceptDontSend"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "ACCEPTED", + noReply: true, + }, + event + ); + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting an invitation without sending a response. + */ +add_task(async function testTentativeWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + await clickMenuAction(win, "imipTentativeButton", "imipTentativeButton_TentativeDontSend"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "TENTATIVE", + noReply: true, + }, + event + ); + + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining an invitation without sending a response. + */ +add_task(async function testDeclineWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + await clickMenuAction(win, "imipDeclineButton", "imipDeclineButton_DeclineDontSend"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "DECLINED", + noReply: true, + }, + event + ); + + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarCancel.js b/comm/calendar/test/browser/invitations/browser_imipBarCancel.js new file mode 100644 index 0000000000..3cde7d4656 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarCancel.js @@ -0,0 +1,129 @@ +/* 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/. */ + +/** + * Tests for processing cancellations via the imip-bar. + */ + +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests accepting a cancellation to an already accepted event. + */ +add_task(async function testCancelAccepted() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + transport, + calendar, + event, + }); +}); + +/** + * Tests accepting a cancellation to tentatively accepted event. + */ +add_task(async function testCancelTentative() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + transport, + calendar, + event, + }); +}); + +/** + * Tests accepting a cancellation to an already declined event. + */ +add_task(async function testCancelDeclined() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + transport, + calendar, + event, + }); +}); + +/** + * Tests the handling of a cancellation when the event was not processed + * previously. + */ +add_task(async function testUnprocessedCancel() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/cancel-single-event.eml")); + let win = await openImipMessage(invite); + + // There should be no buttons present because there is no action to take. + // Note: the imip-bar message "This message contains an event that has already been processed" is + // misleading. + for (let button of [...win.document.querySelectorAll("#imip-view-toolbar > toolbarbutton")]) { + Assert.ok(button.hidden, `${button.id} is hidden`); + } + await BrowserTestUtils.closeWindow(win); +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarEmail.js b/comm/calendar/test/browser/invitations/browser_imipBarEmail.js new file mode 100644 index 0000000000..a3816b65dd --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarEmail.js @@ -0,0 +1,168 @@ +/* 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/. */ + +/** + * Test that the IMIP bar behaves properly for eml files with invites. + */ + +/* eslint-disable @microsoft/sdl/no-insecure-url */ + +function getFileFromChromeURL(leafName) { + let ChromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry); + + let url = Services.io.newURI(getRootDirectory(gTestPath) + leafName); + info(url.spec); + let fileURL = ChromeRegistry.convertChromeURL(url).QueryInterface(Ci.nsIFileURL); + return fileURL.file; +} + +/** + * Test that when opening a message containing a Teams meeting invite + * works as it should. + */ +add_task(async function test_event_from_eml() { + let file = getFileFromChromeURL("data/teams-meeting-invite.eml"); + + let msgWindow = await openMessageFromFile(file); + let aboutMessage = msgWindow.document.getElementById("messageBrowser").contentWindow; + + await TestUtils.waitForCondition( + () => !aboutMessage.document.getElementById("imip-bar").collapsed + ); + info("Ok, iMIP bar is showing"); + + // The contentDocument has both the imipHTMLDetails HTML part generated by us, + // and the regular HTML part generated by the sender (the server). + let links = [ + ...msgWindow.content.document.getElementById("imipHTMLDetails").querySelectorAll("a"), + ]; + + Assert.equal(links.length, 3, "The 3 links should show"); + + // Check the links and their text + Assert.equal( + links[0].href, + "https://teams.microsoft.com/l/meetup-join/19%3ameeting_MGU5NmI2ZGYtOWZmOC00Y2ZmLWJlOTItNjUxNjA5YjUyYTYy%40thread.v2/0?context=%7b%22Tid%22%3a%222fd0c1c5-28e1-40c4-9f0d-a0363ca80a3c%22%2c%22Oid%22%3a%2214464d09-ceb8-458c-a61c-717f1e5c66c5%22%7d", + "link0 href" + ); + Assert.equal( + links[0].textContent, + "", + "link0 textContent" + ); + + Assert.equal(links[1].href, "https://aka.ms/JoinTeamsMeeting", "link1 href"); + Assert.equal(links[1].textContent, "", "link1 textContent"); + + Assert.equal( + links[2].href, + "https://teams.microsoft.com/meetingOptions/?organizerId=14464d09-ceb8-458c-a61c-717f1e5c66c5&tenantId=2fd0c1c5-28e1-40c4-9f0d-a0363ca80a3c&threadId=19_meeting_MGU5NmI2ZGYtOWZmOC00Y2ZmLWJlOTItNjUxNjA5YjUyYTYy@thread.v2&messageId=0&language=fi-FI", + "link2 href" + ); + Assert.equal( + links[2].textContent, + "", + "link2 textContent" + ); + + await BrowserTestUtils.closeWindow(msgWindow); + + Assert.ok(true, "test_event_from_eml test ran to completion"); +}); + +/** + * Test that when opening a message containing a Meet meeting invite + * works as it should. + */ +add_task(async function test_event_from_eml() { + let file = getFileFromChromeURL("data/meet-meeting-invite.eml"); + + let msgWindow = await openMessageFromFile(file); + let aboutMessage = msgWindow.document.getElementById("messageBrowser").contentWindow; + + await TestUtils.waitForCondition( + () => !aboutMessage.document.getElementById("imip-bar").collapsed + ); + info("Ok, iMIP bar is showing"); + + // The contentDocument has both the imipHTMLDetails HTML part generated by us, + // and the regular HTML part generated by the sender (the server). + let links = [ + ...msgWindow.content.document.getElementById("imipHTMLDetails").querySelectorAll("a"), + ]; + + Assert.equal(links.length, 4, "The 4 links should show"); + + // Check the links and their text + Assert.equal(links[0].href, "mailto:foo@example.com", "link0 href"); + Assert.equal(links[0].textContent, "", "link0 textContent"); + + Assert.equal(links[1].href, "http://example.com/?foo=bar", "link1 href"); + Assert.equal(links[1].textContent, "http://example.com?foo=bar", "link1 textContent"); + + Assert.equal(links[2].href, "https://meet.google.com/pyb-ndcu-hhc", "link1 href"); + Assert.equal(links[2].textContent, "https://meet.google.com/pyb-ndcu-hhc", "link1 textContent"); + + Assert.equal( + links[3].href, + "https://calendar.google.com/calendar/event?action=VIEW&eid=NjVtMTdoc2RvbG1vdHYza3ZtcnRnNDBvbnQgbWFnbnVzLm1lbGluQGh1dC5maQ&tok=MjEjYmVydGF0aGVib3RAZ21haWwuY29tZTg2NGFjYmNjYWE1MjVlZWJmY2UzYmRmMDAyNWU0MDkzNDAxZjRhZg&ctz=Europe%2FHelsinki&hl=sv&es=1", + "link2 href" + ); + Assert.equal( + links[3].textContent, + "https://calendar.google.com/calendar/event?action=VIEW&eid=NjVtMTdoc2RvbG1vdHYza3ZtcnRnNDBvbnQgbWFnbnVzLm1lbGluQGh1dC5maQ&tok=MjEjYmVydGF0aGVib3RAZ21haWwuY29tZTg2NGFjYmNjYWE1MjVlZWJmY2UzYmRmMDAyNWU0MDkzNDAxZjRhZg&ctz=Europe%2FHelsinki&hl=sv&es=1", + "link2 textContent" + ); + + await BrowserTestUtils.closeWindow(msgWindow); + + Assert.ok(true, "test_event_from_eml test ran to completion"); +}); + +/** + * Test that when opening a message containing an outlook invite with "empty" + * content works as it should. + */ +add_task(async function test_outlook_event_from_eml() { + let file = getFileFromChromeURL("data/outlook-test-invite.eml"); + + let msgWindow = await openMessageFromFile(file); + let aboutMessage = msgWindow.document.getElementById("messageBrowser").contentWindow; + + await TestUtils.waitForCondition( + () => !aboutMessage.document.getElementById("imip-bar").collapsed + ); + info("Ok, iMIP bar is showing"); + + let details = msgWindow.content.document.getElementById("imipHTMLDetails"); + + Assert.equal( + details.getAttribute("open"), + "open", + "Details should be expanded when the message doesn't include good details" + ); + + await BrowserTestUtils.closeWindow(msgWindow); + + Assert.ok(true, "test_outlook_event_from_eml test ran to completion"); +}); + +/** + * Test that when opening a message containing an event, the IMIP bar shows. + */ +add_task(async function test_event_from_eml() { + let file = getFileFromChromeURL("data/message-containing-event.eml"); + + let msgWindow = await openMessageFromFile(file); + let aboutMessage = msgWindow.document.getElementById("messageBrowser").contentWindow; + + await TestUtils.waitForCondition( + () => !aboutMessage.document.getElementById("imip-bar").collapsed + ); + info("Ok, iMIP bar is showing"); + + await BrowserTestUtils.closeWindow(msgWindow); + + Assert.ok(true, "test_event_from_eml test ran to completion"); +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarExceptionCancel.js b/comm/calendar/test/browser/invitations/browser_imipBarExceptionCancel.js new file mode 100644 index 0000000000..7800e742ca --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarExceptionCancel.js @@ -0,0 +1,137 @@ +/* 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/. */ + +/** + * Tests for processing cancellations to recurring event exceptions. + */ + +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + CalEvent: "resource:///modules/CalEvent.jsm", +}); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + requestLongerTimeout(3); + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests cancelling an exception works. + */ +add_task(async function testCancelException() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + await doCancelExceptionTest({ + calendar, + transport, + identity, + partStat, + recurrenceId: "20220317T110000Z", + isRecurring: true, + }); + } +}); + +/** + * Tests cancelling an event with only an exception processed works. + */ +add_task(async function testCancelExceptionOnly() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + let win = await openImipMessage( + new FileUtils.File(getTestFilePath("data/exception-major.eml")) + ); + await clickAction(win, actionIds.single.button[partStat]); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 5, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + calendar, + event, + transport, + identity, + }); + } +}); + +/** + * Tests processing a cancellation for a recurring event works when only an + * exception was processed previously. + */ +add_task(async function testCancelSeriesWithExceptionOnly() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + let win = await openImipMessage( + new FileUtils.File(getTestFilePath("data/exception-major.eml")) + ); + await clickMenuAction( + win, + actionIds.single.button[partStat], + actionIds.single.noReply[partStat] + ); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 5, 1)).item; + await BrowserTestUtils.closeWindow(win); + + let cancel = new FileUtils.File(getTestFilePath("data/cancel-repeat-event.eml")); + let cancelWin = await openImipMessage(cancel); + let aboutMessage = cancelWin.document.getElementById("messageBrowser").contentWindow; + + let deleteButton = aboutMessage.document.getElementById("imipDeleteButton"); + Assert.ok(!deleteButton.hidden, `#${deleteButton.id} button shown`); + EventUtils.synthesizeMouseAtCenter(deleteButton, {}, aboutMessage); + await BrowserTestUtils.closeWindow(cancelWin); + await CalendarTestUtils.monthView.waitForNoItemAt(window, 3, 5, 1); + Assert.ok(!(await calendar.getItem(event.id)), "event was deleted"); + + Assert.equal( + transport.sentItems.length, + 0, + "itip subsystem did not attempt to send a response" + ); + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); + } +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarExceptionOnly.js b/comm/calendar/test/browser/invitations/browser_imipBarExceptionOnly.js new file mode 100644 index 0000000000..88ad0b3c41 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarExceptionOnly.js @@ -0,0 +1,262 @@ +/* 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/. */ + +/** + * Tests for receiving an invitation exception but the original event was not + * processed first. + */ +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + requestLongerTimeout(5); + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests accepting a minor exception and sending a response. + */ +add_task(async function testMinorAcceptWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-minor.eml"))); + await clickAction(win, "imipAcceptButton"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "ACCEPTED", + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting a minor exception and sending a response. + */ +add_task(async function testMinorTentativeWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-minor.eml"))); + await clickAction(win, "imipTentativeButton"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "TENTATIVE", + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining a minor exception and sending a response. + */ +add_task(async function testMinorDeclineWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-minor.eml"))); + await clickAction(win, "imipDeclineButton"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "DECLINED", + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests accepting a minor exception without sending a response. + */ +add_task(async function testMinorAcceptWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-minor.eml"))); + await clickMenuAction(win, "imipAcceptButton", "imipAcceptButton_AcceptDontSend"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "ACCEPTED", + noReply: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting a minor exception without sending a response. + */ +add_task(async function testMinorTentativeWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-minor.eml"))); + await clickMenuAction(win, "imipTentativeButton", "imipTentativeButton_TentativeDontSend"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "TENTATIVE", + noReply: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining a minor exception without sending a response. + */ +add_task(async function testMinorDeclineWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-minor.eml"))); + await clickMenuAction(win, "imipDeclineButton", "imipDeclineButton_DeclineDontSend"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "DECLINED", + noReply: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests accepting a major exception and sending a response. + */ +add_task(async function testMajorAcceptWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-major.eml"))); + await clickAction(win, "imipAcceptButton"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "ACCEPTED", + isMajor: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting a major exception and sending a response. + */ +add_task(async function testMajorTentativeWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-major.eml"))); + await clickAction(win, "imipTentativeButton"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "TENTATIVE", + isMajor: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining a major exception and sending a response. + */ +add_task(async function testMajorDeclineWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-major.eml"))); + await clickAction(win, "imipDeclineButton"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "DECLINED", + isMajor: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests accepting a major exception without sending a response. + */ +add_task(async function testMajorAcceptWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-major.eml"))); + await clickMenuAction(win, "imipAcceptButton", "imipAcceptButton_AcceptDontSend"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "ACCEPTED", + noReply: true, + isMajor: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting a major exception without sending a response. + */ +add_task(async function testMajorTentativeWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-major.eml"))); + await clickMenuAction(win, "imipTentativeButton", "imipTentativeButton_TentativeDontSend"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "TENTATIVE", + noReply: true, + isMajor: true, + }); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining a major exception without sending a response. + */ +add_task(async function testMajorDeclineWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-major.eml"))); + await clickMenuAction(win, "imipDeclineButton", "imipDeclineButton_DeclineDontSend"); + await doExceptionOnlyTest({ + calendar, + transport, + identity, + partStat: "DECLINED", + noReply: true, + isMajor: true, + }); + await BrowserTestUtils.closeWindow(win); +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarExceptions.js b/comm/calendar/test/browser/invitations/browser_imipBarExceptions.js new file mode 100644 index 0000000000..2cdf18ed59 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarExceptions.js @@ -0,0 +1,288 @@ +/* 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/. */ + +/** + * Tests for handling exceptions to recurring event invitations via the imip-bar. + */ + +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +XPCOMUtils.defineLazyModuleGetters(this, { + CalEvent: "resource:///modules/CalEvent.jsm", +}); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + requestLongerTimeout(5); + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests a minor update exception to an already accepted recurring event. + */ +add_task(async function testMinorUpdateExceptionToAccepted() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorExceptionTest({ + transport, + calendar, + partStat: "ACCEPTED", + }); +}); + +/** + * Tests a minor update exception to an already tentatively accepted recurring + * event. + */ +add_task(async function testMinorUpdateExceptionToTentative() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorExceptionTest({ + transport, + calendar, + partStat: "TENTATIVE", + }); +}); + +/** + * Tests a minor update exception to an already declined recurring declined + * event. + */ +add_task(async function testMinorUpdateExceptionToDeclined() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorExceptionTest({ + transport, + calendar, + partStat: "DECLINED", + }); +}); + +/** + * Tests a major update exception to an already accepted event. + */ +add_task(async function testMajorExceptionToAcceptedWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorExceptionTest({ + transport, + identity, + calendar, + partStat, + }); + } +}); + +/** + * Tests a major update exception to an already tentatively accepted event. + */ +add_task(async function testMajorExceptionToTentativeWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorExceptionTest({ + transport, + identity, + calendar, + partStat, + }); + } +}); + +/** + * Tests a major update exception to an already declined event. + */ +add_task(async function testMajorExceptionToDeclinedWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorExceptionTest({ + transport, + identity, + calendar, + isRecurring: true, + partStat, + }); + } +}); + +/** + * Tests a major update exception to an already accepted event without sending + * a reply. + */ +add_task(async function testMajorExecptionToAcceptedWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickMenuAction( + win, + "imipAcceptRecurrencesButton", + "imipAcceptRecurrencesButton_AcceptDontSend" + ); + + await BrowserTestUtils.closeWindow(win); + await doMajorExceptionTest({ + transport, + calendar, + isRecurring: true, + partStat, + noReply: true, + }); + } +}); + +/** + * Tests a major update exception to an already tentatively accepted event + * without sending a reply. + */ +add_task(async function testMajorUpdateToTentativeWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickMenuAction( + win, + "imipTentativeRecurrencesButton", + "imipTentativeRecurrencesButton_TentativeDontSend" + ); + + await BrowserTestUtils.closeWindow(win); + await doMajorExceptionTest({ + transport, + calendar, + isRecurring: true, + partStat, + noReply: true, + }); + } +}); + +/** + * Tests a major update exception to a declined event without sending a reply. + */ +add_task(async function testMajorUpdateToDeclinedWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickMenuAction( + win, + "imipDeclineRecurrencesButton", + "imipDeclineRecurrencesButton_DeclineDontSend" + ); + + await BrowserTestUtils.closeWindow(win); + await doMajorExceptionTest({ + transport, + calendar, + isRecurring: true, + partStat, + noReply: true, + }); + } +}); + +/** + * Tests a major update exception to an event where the participation status + * is still "NEEDS-ACTION". Here we want to ensure action is only taken on the + * target exception date and not the other dates. + */ +add_task(async function testMajorUpdateToNeedsAction() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + + // Extract the event from the .eml file and manually add it to the calendar. + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let srcText = await IOUtils.readUTF8(invite.path); + let ics = srcText.match( + /--00000000000080f3da05db4aef59[\S\s]+--00000000000080f3da05db4aef59/g + )[0]; + ics = ics.split("--00000000000080f3da05db4aef59").join(""); + ics = ics.replaceAll(/Content-(Type|Transfer-Encoding)?: .*/g, ""); + + let event = new CalEvent(ics); + + // This will not be set because we manually added the event. + event.setProperty("x-moz-received-dtstamp", "20220316T191602Z"); + + await calendar.addItem(event); + await CalendarTestUtils.monthView.waitForItemAt(window, 3, 5, 1).item; + await doMajorExceptionTest({ + transport, + identity, + calendar, + isRecurring: true, + partStat, + }); + } +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarRepeat.js b/comm/calendar/test/browser/invitations/browser_imipBarRepeat.js new file mode 100644 index 0000000000..c14ff2c0a5 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarRepeat.js @@ -0,0 +1,218 @@ +/* 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/. */ + +/** + * Tests for receiving recurring event invitations via the imip-bar. + */ +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests accepting an invitation to a recurring event and sending a response. + */ +add_task(async function testAcceptRecurringWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipAcceptRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + isRecurring: true, + partStat: "ACCEPTED", + }, + event + ); + + await calendar.deleteItem(event.parentItem); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting an invitation to a recurring event and sending a + * response. + */ +add_task(async function testTentativeRecurringWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipTentativeRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + isRecurring: true, + partStat: "TENTATIVE", + }, + event + ); + + await calendar.deleteItem(event.parentItem); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining an invitation to a recurring event and sending a response. + */ +add_task(async function testDeclineRecurringWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipDeclineRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + + await doImipBarActionTest( + { + calendar, + transport, + identity, + isRecurring: true, + partStat: "DECLINED", + }, + event + ); + + await calendar.deleteItem(event.parentItem); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests accepting an invitation to a recurring event without sending a response. + */ +add_task(async function testAcceptRecurringWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickMenuAction( + win, + "imipAcceptRecurrencesButton", + "imipAcceptRecurrencesButton_AcceptDontSend" + ); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + isRecurring: true, + partStat: "ACCEPTED", + noReply: true, + }, + event + ); + + await calendar.deleteItem(event.parentItem); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting an invitation to a recurring event without sending + * a response. + */ +add_task(async function testTentativeRecurringWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickMenuAction( + win, + "imipTentativeRecurrencesButton", + "imipTentativeRecurrencesButton_TentativeDontSend" + ); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + isRecurring: true, + partStat: "TENTATIVE", + noReply: true, + }, + event + ); + + await calendar.deleteItem(event.parentItem); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining an invitation to a recurring event without sending a response. + */ +add_task(async function testDeclineRecurrencesWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickMenuAction( + win, + "imipDeclineRecurrencesButton", + "imipDeclineRecurrencesButton_DeclineDontSend" + ); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + isRecurring: true, + partStat: "DECLINED", + noReply: true, + }, + event + ); + + await calendar.deleteItem(event.parentItem); + await BrowserTestUtils.closeWindow(win); +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarRepeatCancel.js b/comm/calendar/test/browser/invitations/browser_imipBarRepeatCancel.js new file mode 100644 index 0000000000..1ab50cc739 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarRepeatCancel.js @@ -0,0 +1,186 @@ +/* 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/. */ + +/** + * Tests for processing cancellations to recurring invitations via the imip-bar. + */ +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + requestLongerTimeout(5); + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests accepting a cancellation to an already accepted recurring event. + */ +add_task(async function testCancelAcceptedRecurring() { + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipAcceptRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + calendar, + event, + transport, + isRecurring: true, + }); +}); + +/** + * Tests accepting a cancellation to an already tentatively accepted event. + */ +add_task(async function testCancelTentativeRecurring() { + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipTentativeRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + calendar, + event, + transport, + identity, + isRecurring: true, + }); +}); + +/** + * Tests accepting a cancellation to an already declined recurring event. + */ +add_task(async function testCancelDeclinedRecurring() { + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipDeclineRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + calendar, + event, + transport, + identity, + isRecurring: true, + }); +}); + +/** + * Tests accepting a cancellation to a single occurrence of an already accepted + * recurring event. + */ +add_task(async function testCancelAcceptedOccurrence() { + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipAcceptRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + calendar, + event, + transport, + isRecurring: true, + recurrenceId: "20220317T110000Z", + }); + await calendar.deleteItem(event.parentItem); +}); + +/** + * Tests accepting a cancellation to a single occurrence of an already tentatively + * accepted event. + */ +add_task(async function testCancelTentativeOccurrence() { + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipTentativeRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + calendar, + event, + transport, + identity, + isRecurring: true, + recurrenceId: "20220317T110000Z", + }); + await calendar.deleteItem(event.parentItem); +}); + +/** + * Tests accepting a cancellation to a single occurrence of an already declined + * recurring event. + */ +add_task(async function testCancelDeclinedOccurrence() { + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/repeat-event.eml"))); + await clickAction(win, "imipDeclineRecurrencesButton"); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await BrowserTestUtils.closeWindow(win); + await doCancelTest({ + calendar, + event, + transport, + identity, + isRecurring: true, + recurrenceId: "20220317T110000Z", + }); + await calendar.deleteItem(event.parentItem); +}); + +/** + * Tests the handling of a cancellation when the event was not processed + * previously. + */ +add_task(async function testUnprocessedCancel() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/cancel-repeat-event.eml")); + let win = await openImipMessage(invite); + for (let button of [...win.document.querySelectorAll("#imip-view-toolbar > toolbarbutton")]) { + Assert.ok(button.hidden, `${button.id} is hidden`); + } + await BrowserTestUtils.closeWindow(win); +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarRepeatUpdates.js b/comm/calendar/test/browser/invitations/browser_imipBarRepeatUpdates.js new file mode 100644 index 0000000000..7f0d16f627 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarRepeatUpdates.js @@ -0,0 +1,247 @@ +/* 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/. */ + +/** + * Tests for receiving minor and major updates to recurring event invitations + * via the imip-bar. + */ + +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + requestLongerTimeout(5); + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests a minor update to an already accepted event. + */ +add_task(async function testMinorUpdateToAccepted() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorUpdateTest({ + transport, + calendar, + isRecurring: true, + partStat: "ACCEPTED", + }); +}); + +/** + * Tests a minor update to an already tentatively accepted event. + */ +add_task(async function testMinorUpdateToTentative() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorUpdateTest({ + transport, + calendar, + isRecurring: true, + partStat: "TENTATIVE", + }); +}); + +/** + * Tests a minor update to an already declined event. + */ +add_task(async function testMinorUpdateToDeclined() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorUpdateTest({ transport, calendar, isRecurring: true, invite, partStat: "DECLINED" }); +}); + +/** + * Tests a major update to an already accepted event. + */ +add_task(async function testMajorUpdateToAcceptedWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + identity, + calendar, + isRecurring: true, + partStat, + }); + } +}); + +/** + * Tests a major update to an already tentatively accepted event. + */ +add_task(async function testMajorUpdateToTentativeWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + identity, + calendar, + isRecurring: true, + partStat, + }); + } +}); + +/** + * Tests a major update to an already declined event. + */ +add_task(async function testMajorUpdateToDeclinedWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineRecurrencesButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + identity, + calendar, + isRecurring: true, + partStat, + }); + } +}); + +/** + * Tests a major update to an already accepted event without replying to the + * update. + */ +add_task(async function testMajorUpdateToAcceptedWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickMenuAction( + win, + "imipAcceptRecurrencesButton", + "imipAcceptRecurrencesButton_AcceptDontSend" + ); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + calendar, + isRecurring: true, + partStat, + noReply: true, + }); + } +}); + +/** + * Tests a major update to an already tentatively accepted event without replying + * to the update. + */ +add_task(async function testMajorUpdateToTentativeWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickMenuAction( + win, + "imipTentativeRecurrencesButton", + "imipTentativeRecurrencesButton_TentativeDontSend" + ); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + calendar, + isRecurring: true, + partStat, + noReply: true, + }); + } +}); + +/** + * Tests a major update to an already declined event. + */ +add_task(async function testMajorUpdateToDeclinedWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickMenuAction( + win, + "imipDeclineRecurrencesButton", + "imipDeclineRecurrencesButton_DeclineDontSend" + ); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + calendar, + isRecurring: true, + partStat, + noReply: true, + }); + } +}); diff --git a/comm/calendar/test/browser/invitations/browser_imipBarUpdates.js b/comm/calendar/test/browser/invitations/browser_imipBarUpdates.js new file mode 100644 index 0000000000..d0f5018e89 --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_imipBarUpdates.js @@ -0,0 +1,223 @@ +/* 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/. */ + +/** + * Tests for receiving minor and major updates to invitations via the imip-bar. + */ + +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + requestLongerTimeout(5); + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Tests a minor update to an already accepted event. + */ +add_task(async function testMinorUpdateToAccepted() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorUpdateTest({ + transport, + calendar, + partStat: "ACCEPTED", + }); +}); + +/** + * Tests a minor update to an already tentatively accepted event. + */ +add_task(async function testMinorUpdateToTentative() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorUpdateTest({ transport, calendar, invite, partStat: "TENTATIVE" }); +}); + +/** + * Tests a minor update to an already declined event. + */ +add_task(async function testMinorUpdateToDeclined() { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineButton"); + + await BrowserTestUtils.closeWindow(win); + await doMinorUpdateTest({ transport, calendar, invite, partStat: "DECLINED" }); +}); + +/** + * Tests a major update to an already accepted event. + */ +add_task(async function testMajorUpdateToAcceptedWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + identity, + calendar, + partStat, + }); + } +}); + +/** + * Tests a major update to an already tentatively accepted event. + */ +add_task(async function testMajorUpdateToTentativeWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + identity, + calendar, + partStat, + }); + } +}); + +/** + * Tests a major update to an already declined event. + */ +add_task(async function testMajorUpdateToDeclinedWithResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + identity, + calendar, + partStat, + }); + } +}); + +/** + * Tests a major update to an already accepted event without replying to the + * update. + */ +add_task(async function testMajorUpdateToAcceptedWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipAcceptButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + calendar, + partStat, + noReply: true, + }); + } +}); + +/** + * Tests a major update to an already tentatively accepted event without replying + * to the update. + */ +add_task(async function testMajorUpdateToTentativeWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipTentativeButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + calendar, + partStat, + noReply: true, + }); + } +}); + +/** + * Tests a major update to an already declined event. + */ +add_task(async function testMajorUpdateToDeclinedWithoutResponse() { + for (let partStat of ["ACCEPTED", "TENTATIVE", "DECLINED"]) { + transport.reset(); + let invite = new FileUtils.File(getTestFilePath("data/single-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, "imipDeclineButton"); + + await BrowserTestUtils.closeWindow(win); + await doMajorUpdateTest({ + transport, + calendar, + partStat, + noReply: true, + }); + } +}); diff --git a/comm/calendar/test/browser/invitations/browser_invitationDisplayNew.js b/comm/calendar/test/browser/invitations/browser_invitationDisplayNew.js new file mode 100644 index 0000000000..a7b3f833de --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_invitationDisplayNew.js @@ -0,0 +1,257 @@ +/* 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/. */ + +/** + * Tests for the invitation panel display with new events. + */ +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { CalItipDefaultEmailTransport } = ChromeUtils.import( + "resource:///modules/CalItipEmailTransport.jsm" +); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let identity; +let calendar; +let transport; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + transport = new EmailTransport(account, identity); + + let getImipTransport = cal.itip.getImipTransport; + cal.itip.getImipTransport = () => transport; + + let deleteMgr = Cc["@mozilla.org/calendar/deleted-items-manager;1"].getService( + Ci.calIDeletedItems + ).wrappedJSObject; + let markDeleted = deleteMgr.markDeleted; + deleteMgr.markDeleted = () => {}; + + Services.prefs.setBoolPref("calendar.itip.newInvitationDisplay", true); + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + cal.itip.getImipTransport = getImipTransport; + deleteMgr.markDeleted = markDeleted; + CalendarTestUtils.removeCalendar(calendar); + Services.prefs.setBoolPref("calendar.itip.newInvitationDisplay", false); + }); +}); + +/** + * Tests the invitation panel shows the correct data when loaded with a new + * invitation. + */ +add_task(async function testShowPanelData() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + let panel = win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"); + + if (panel.ownerDocument.hasPendingL10nMutations) { + await BrowserTestUtils.waitForEvent(panel.ownerDocument, "L10nMutationsFinished"); + } + + let notification = panel.shadowRoot.querySelector("notification-message"); + compareShownPanelValues(notification.shadowRoot, { + ".notification-message": "You have been invited to this event.", + ".notification-button-container > button": "More", + }); + + compareShownPanelValues(panel.shadowRoot, { + "#title": "Single Event", + "#location": "Somewhere", + "#partStatTotal": "3 participants", + '[data-l10n-id="calendar-invitation-panel-partstat-accepted"]': "1 yes", + '[data-l10n-id="calendar-invitation-panel-partstat-needs-action"]': "2 pending", + "#attendees li:nth-of-type(1)": "Sender ", + "#attendees li:nth-of-type(2)": "Receiver ", + "#attendees li:nth-of-type(3)": "Other ", + "#description": "An event invitation.", + }); + + Assert.ok(!panel.shadowRoot.querySelector("#actionButtons").hidden, "action buttons shown"); + for (let indicator of [ + ...panel.shadowRoot.querySelectorAll("calendar-invitation-change-indicator"), + ]) { + Assert.ok(indicator.hidden, `${indicator.id} is hidden`); + } + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests accepting an invitation and sending a response. + */ +add_task(async function testAcceptWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + let panel = win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"); + + await clickPanelAction(panel, "acceptButton"); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "ACCEPTED", + }, + event + ); + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting an invitation and sending a response. + */ +add_task(async function testTentativeWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + let panel = win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"); + + await clickPanelAction(panel, "tentativeButton"); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "TENTATIVE", + }, + event + ); + + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining an invitation and sending a response. + */ +add_task(async function testDeclineWithResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + let panel = win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"); + + await clickPanelAction(panel, "declineButton"); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "DECLINED", + }, + event + ); + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests accepting an invitation without sending a response. + */ +add_task(async function testAcceptWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + let panel = win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"); + + await clickPanelAction(panel, "acceptButton", false); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "ACCEPTED", + noSend: true, + }, + event + ); + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests tentatively accepting an invitation without sending a response. + */ +add_task(async function testTentativeWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + let panel = win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"); + + await clickPanelAction(panel, "tentativeButton", false); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "TENTATIVE", + noSend: true, + }, + event + ); + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); + +/** + * Tests declining an invitation without sending a response. + */ +add_task(async function testDeclineWithoutResponse() { + transport.reset(); + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/single-event.eml"))); + let panel = win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"); + + await clickPanelAction(panel, "declineButton", false); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + await doImipBarActionTest( + { + calendar, + transport, + identity, + partStat: "DECLINED", + noSend: true, + }, + event + ); + await calendar.deleteItem(event); + await BrowserTestUtils.closeWindow(win); +}); diff --git a/comm/calendar/test/browser/invitations/browser_unsupportedFreq.js b/comm/calendar/test/browser/invitations/browser_unsupportedFreq.js new file mode 100644 index 0000000000..2d05ed66dc --- /dev/null +++ b/comm/calendar/test/browser/invitations/browser_unsupportedFreq.js @@ -0,0 +1,107 @@ +/* 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/. */ + +/** + * Tests for ensuring the application does not hang after processing an + * unsupported FREQ value. + */ +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +let calendar; + +/** + * Initialize account, identity and calendar. + */ +add_setup(async function () { + let account = MailServices.accounts.createAccount(); + account.incomingServer = MailServices.accounts.createIncomingServer( + "receiver", + "example.com", + "imap" + ); + + let identity = MailServices.accounts.createIdentity(); + identity.email = "receiver@example.com"; + account.addIdentity(identity); + + await CalendarTestUtils.setCalendarView(window, "month"); + window.goToDate(cal.createDateTime("20220316T191602Z")); + + calendar = CalendarTestUtils.createCalendar("Test"); + registerCleanupFunction(() => { + MailServices.accounts.removeAccount(account, true); + CalendarTestUtils.removeCalendar(calendar); + }); +}); + +/** + * Runs the test using the provided FREQ value. + * + * @param {string} freq Either "SECONDLY" or "MINUTELY" + */ +async function doFreqTest(freq) { + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let srcText = await IOUtils.readUTF8(invite.path); + let tmpFile = FileTestUtils.getTempFile(`${freq}.eml`); + + srcText = srcText.replace(/RRULE:.*/g, `RRULE:FREQ=${freq}`); + srcText = srcText.replace(/UID:.*/g, `UID:${freq}`); + await IOUtils.writeUTF8(tmpFile.path, srcText); + + let win = await openImipMessage(tmpFile); + await clickMenuAction( + win, + "imipAcceptRecurrencesButton", + "imipAcceptRecurrencesButton_AcceptDontSend" + ); + + // Give the view time to refresh and create any occurrences. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 5000)); + await BrowserTestUtils.closeWindow(win); + + let dayBoxItems = document.querySelectorAll("calendar-month-day-box-item"); + Assert.equal(dayBoxItems.length, 1, "only one occurrence displayed"); + + let [dayBox] = dayBoxItems; + let { item } = dayBox; + Assert.equal(item.title, "Repeat Event"); + Assert.equal(item.startDate.icalString, "20220316T110000Z"); + + let summaryDialog = await CalendarTestUtils.viewItem(window, dayBox); + Assert.equal( + summaryDialog.document.querySelector(".repeat-details").textContent, + "Repeat details unknown", + "repeat details not shown" + ); + + await BrowserTestUtils.closeWindow(summaryDialog); + await calendar.deleteItem(item.parentItem); + await TestUtils.waitForCondition( + () => document.querySelectorAll("calendar-month-day-box-item").length == 0 + ); +} + +/** + * Tests accepting an invitation using the FREQ=SECONDLY value does not render + * the application unusable. + */ +add_task(async function testSecondly() { + return doFreqTest("SECONDLY"); +}); + +/** + * Tests accepting an invitation using the FREQ=MINUTELY value does not render + * the application unusable. + */ +add_task(async function testMinutely() { + return doFreqTest("MINUTELY"); +}); diff --git a/comm/calendar/test/browser/invitations/data/cancel-repeat-event.eml b/comm/calendar/test/browser/invitations/data/cancel-repeat-event.eml new file mode 100644 index 0000000000..03f298525b --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/cancel-repeat-event.eml @@ -0,0 +1,49 @@ +MIME-Version: 1.0 +Date: Mon, 28 Mar 2022 17:49:35 +0000 +Subject: Invitation: Repeat Event @ Daily from 2pm to 3pm 3 times (AST) (receiver@example.com) +From: sender@example.com +To: receiver@example.com +Content-Type: multipart/mixed; boundary="00000000000080f3db05db4aef5b" + +--00000000000080f3db05db4aef5b +Content-Type: multipart/alternative; boundary="00000000000080f3da05db4aef59" + +--00000000000080f3da05db4aef59 +Content-Type: text/calendar; charset="UTF-8"; method=CANCEL +Content-Transfer-Encoding: 7bit + +BEGIN:VCALENDAR +METHOD:CANCEL +BEGIN:VEVENT +DTSTART:20220316T110000Z +DTEND:20220316T113000Z +RRULE:FREQ=DAILY;WKST=SU;COUNT=3;INTERVAL=1 +DTSTAMP:20220316T191602Z +UID:02e79b96 +ORGANIZER;CN=Sender; + EMAIL=sender@example.com:mailto:sender@example.com +ATTENDEE;CN=Sender; + EMAIL=sender@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=ACCEPTED:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=receiver@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION:mailto:other@example.com +CREATED:20220328T174934Z +LAST-MODIFIED:20220328T174934Z +LOCATION:Somewhere +SEQUENCE:1 +STATUS:CANCELLED +SUMMARY:Repeat Event +DESCRIPTION:An event invitation. +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR + +--00000000000080f3da05db4aef59-- +--00000000000080f3db05db4aef5b-- diff --git a/comm/calendar/test/browser/invitations/data/cancel-single-event.eml b/comm/calendar/test/browser/invitations/data/cancel-single-event.eml new file mode 100644 index 0000000000..afb4edb99d --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/cancel-single-event.eml @@ -0,0 +1,78 @@ +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/mixed; boundary="_----------=_1647458162153312762582" +Date: Wed, 16 Mar 2022 15:16:02 -0400 +To: receiver@example.com +Subject: Cancellation: Single Event @ Wed, Mar 16 2022 11:00 AST +From: Sender + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762582 +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/alternative; boundary="_----------=_1647458162153312762583" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Length: 227 +Content-Transfer-Encoding: binary +Content-Type: text/plain; charset="utf-8" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +Single Event + +When: + Wed, Mar 16 2022 + 11:00 - 12:00 AST +Where: + Somewhere + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Transfer-Encoding: quoted-printable +Content-Type: text/calendar; charset="utf-8"; method=CANCEL +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:CANCEL +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:02e79b96 +SEQUENCE:1 +DTSTAMP:20220317T191602Z +CREATED:20220316T191532Z +DTSTART:20220316T110000Z +DTEND:20220316T113000Z +DURATION:PT1H +PRIORITY:0 +SUMMARY:Single Event +DESCRIPTION:An event invitation. +LOCATION:Somewhere +STATUS:CANCELLED +TRANSP:OPAQUE +CLASS:PUBLIC +ORGANIZER;CN=3DSender; + EMAIL=3Dsender@example.com:mailto:sender@example.com +ATTENDEE;CN=3DSender; + EMAIL=3Dsender@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DACCEPTED:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=3Dreceiver@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION:mailto:other@example.com +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR + +--_----------=_1647458162153312762583-- diff --git a/comm/calendar/test/browser/invitations/data/exception-major.eml b/comm/calendar/test/browser/invitations/data/exception-major.eml new file mode 100644 index 0000000000..07f48e64bd --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/exception-major.eml @@ -0,0 +1,49 @@ +MIME-Version: 1.0 +Date: Mon, 28 Mar 2022 17:49:35 +0000 +Subject: Exception Major +From: sender@example.com +To: receiver@example.com +Content-Type: multipart/mixed; boundary="00000000000080f3db05db4aef5b" + +--00000000000080f3db05db4aef5b +Content-Type: multipart/alternative; boundary="00000000000080f3da05db4aef59" + +--00000000000080f3da05db4aef59 +Content-Type: text/calendar; charset="UTF-8"; method=REQUEST +Content-Transfer-Encoding: 7bit + +BEGIN:VCALENDAR +METHOD:REQUEST +BEGIN:VEVENT +DTSTART:20220317T050000Z +DTEND:20220317T053000Z +RECURRENCE-ID:20220317T110000Z +DTSTAMP:20220316T191602Z +UID:02e79b96 +ORGANIZER;CN=Sender; + EMAIL=sender@example.com:mailto:sender@example.com +ATTENDEE;CN=Sender; + EMAIL=sender@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=receiver@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:other@example.com +CREATED:20220328T174934Z +LAST-MODIFIED:20220328T174934Z +LOCATION:Somewhere +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:Repeat Event +DESCRIPTION:An event invitation. +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR + +--00000000000080f3da05db4aef59-- +--00000000000080f3db05db4aef5b-- diff --git a/comm/calendar/test/browser/invitations/data/exception-minor.eml b/comm/calendar/test/browser/invitations/data/exception-minor.eml new file mode 100644 index 0000000000..7cc38d29d3 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/exception-minor.eml @@ -0,0 +1,49 @@ +MIME-Version: 1.0 +Date: Mon, 28 Mar 2022 17:49:35 +0000 +Subject: Exception Minor +From: sender@example.com +To: receiver@example.com +Content-Type: multipart/mixed; boundary="00000000000080f3db05db4aef5b" + +--00000000000080f3db05db4aef5b +Content-Type: multipart/alternative; boundary="00000000000080f3da05db4aef59" + +--00000000000080f3da05db4aef59 +Content-Type: text/calendar; charset="UTF-8"; method=REQUEST +Content-Transfer-Encoding: 7bit + +BEGIN:VCALENDAR +METHOD:REQUEST +BEGIN:VEVENT +DTSTART:20220317T110000Z +DTEND:20220317T113000Z +RECURRENCE-ID:20220317T110000Z +DTSTAMP:20220318T191602Z +UID:02e79b96 +ORGANIZER;CN=Sender; + EMAIL=sender@example.com:mailto:sender@example.com +ATTENDEE;CN=Sender; + EMAIL=sender@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=receiver@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:other@example.com +CREATED:20220328T174934Z +LAST-MODIFIED:20220328T174934Z +LOCATION:Exception location +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Exception title +DESCRIPTION:Exception description +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:Exception description. +END:VALARM +END:VEVENT +END:VCALENDAR + +--00000000000080f3da05db4aef59-- +--00000000000080f3db05db4aef5b-- diff --git a/comm/calendar/test/browser/invitations/data/meet-meeting-invite.eml b/comm/calendar/test/browser/invitations/data/meet-meeting-invite.eml new file mode 100644 index 0000000000..8587cd803a --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/meet-meeting-invite.eml @@ -0,0 +1,384 @@ +Sender: Google Kalender +Message-ID: <0000000000008c6d7005be1c767c@google.com> +Date: Mon, 22 Mar 2021 09:12:20 +0000 +Subject: Meet invite (HTML) +From: example@gmail.com +To: homer@example.com +Content-Type: multipart/mixed; boundary="0000000000008c6d6205be1c767e" +Return-Path: example@gmail.com +MIME-Version: 1.0 + +--0000000000008c6d6205be1c767e +Content-Type: multipart/alternative; boundary="0000000000008c6d6005be1c767c" + +--0000000000008c6d6005be1c767c +Content-Type: text/plain; charset="UTF-8"; format=flowed; delsp=yes +Content-Transfer-Encoding: base64 + +RHUgaGFyIGJsaXZpdCBpbmJqdWRlbiB0aWxsIGbDtmxqYW5kZSBow6RuZGVsc2UuCgpUaXRlbDog +TWVlZWVldCBtZSBIVE1MClRoaXMgaXMgYSB0ZXN0LiBCb2xkLiBJdGFsaWMuJm5ic3A7V2lsbCBk +aXNjdXNzIGFkZHJlc3MgZm9yIGVtYWlsICAKJmx0O2Zvb0BleGFtcGxlLmNvbSZndDsgYW5kIGh0 +dHA6Ly9leGFtcGxlLmNvbT9mb289YmFyLgpOw6RyOiBtw6VuIGRlbiAyMiBtYXJzIDIwMjEgMTE6 +MzBhbSDigJMgMTI6MzBwbSDDlnN0ZXVyb3BlaXNrIHRpZCAtIEhlbHNpbmdmb3JzCgpBbnNsdXRu +aW5nc2luZm86IEFuc2x1dCB0aWxsIEdvb2dsZSBNZWV0Cmh0dHBzOi8vbWVldC5nb29nbGUuY29t +L3B5Yi1uZGN1LWhoYz9ocz0yMjQKCkthbGVuZGVyOiBob21lckBleGFtcGxlLmNvbQpWZW06CiAg +ICAgKiBleGFtcGxlQGdtYWlsLmNvbeKAkyBvcmdhbmlzYXTDtnIKICAgICAqIGhvbWVyQGV4YW1w +bGUuY29tCgpJbmZvcm1hdGlvbiBvbSBow6RuZGVsc2VuOiAgCmh0dHBzOi8vY2FsZW5kYXIuZ29v +Z2xlLmNvbS9jYWxlbmRhci9ldmVudD9hY3Rpb249VklFVyZlaWQ9TmpWdE1UZG9jMlJ2YkcxdmRI +WXphM1p0Y25Sbk5EQnZiblFnYldGbmJuVnpMbTFsYkdsdVFHaDFkQzVtYVEmdG9rPU1qRWpZbVZ5 +ZEdGMGFHVmliM1JBWjIxaGFXd3VZMjl0WlRnMk5HRmpZbU5qWVdFMU1qVmxaV0ptWTJVelltUm1N +REF5TldVME1Ea3pOREF4WmpSaFpnJmN0ej1FdXJvcGUlMkZIZWxzaW5raSZobD1zdiZlcz0wCgpJ +bmJqdWRhbiBmcsOlbiBHb29nbGUgS2FsZW5kZXI6IGh0dHBzOi8vY2FsZW5kYXIuZ29vZ2xlLmNv +bS9jYWxlbmRhci8KCkRldHRhIGUtcG9zdG1lZGRlbGFuZGUgaGFyIHNraWNrYXRzIHRpbGwga29u +dG90IGhvbWVyQGV4YW1wbGUuY29tICAKZWZ0ZXJzb20gZHUgw6RyIGRlbHRhZ2FyZSB2aWQgZGVu +bmEgaMOkbmRlbHNlLgoKT20gZHUgaW50ZSB2aWxsIGbDpSB1cHBkYXRlcmluZ2FyIG9tIGRlbm5h +IGjDpG5kZWxzZSBpIGZyYW10aWRlbiBrYW4gZHUgdGFja2EgIApuZWogdGlsbCBkZW5uYSBow6Ru +ZGVsc2UuIER1IGthbiDDpHZlbiByZWdpc3RyZXJhIGRpZyBmw7ZyIGF0dCBmw6UgZXR0ICAKR29v +Z2xlLWtvbnRvIHDDpSBodHRwczovL2NhbGVuZGFyLmdvb2dsZS5jb20vY2FsZW5kYXIvIG9jaCBr +b250cm9sbGVyYSAgCmF2aXNlcmluZ3NpbnN0w6RsbG5pbmdhcm5hIGbDtnIgaGVsYSBrYWxlbmRl +cm4uCgpPbSBkdSB2aWRhcmViZWZvcmRyYXIgZGVuIGjDpHIgaW5ianVkYW4ga2FuIGRldCBnw7Zy +YSBkZXQgbcO2amxpZ3QgZsO2ciBhbGxhICAKbW90dGFnYXJlIGF0dCBza2lja2EgZXR0IHN2YXIg +dGlsbCBvcmdhbmlzYXTDtnJlbiBvY2ggbMOkZ2dhcyB0aWxsIHDDpSAgCmfDpHN0bGlzdGFuLCBi +anVkYSBpbiBhbmRyYSBvYXZzZXR0IGRlcmFzIGVnZW4gaW5ianVkbmluZ3NzdGF0dXMgZWxsZXIg +IAptb2RpZmllcmEgZGl0dCBPU0EuIEzDpHMgbWVyIHDDpSAgCmh0dHBzOi8vc3VwcG9ydC5nb29n +bGUuY29tL2NhbGVuZGFyL2Fuc3dlci8zNzEzNSNmb3J3YXJkaW5nCg== +--0000000000008c6d6005be1c767c +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + + + + + + + + + +
+ + + + + + + + + +
+ +

+Du har blivit inbjuden till f=C3=B6ljande h=C3=A4ndelse. +
+ + + +

+Meeeeet me HTML

+ + + + + + + + + + + + + + + + + + + + + + + +
+
N=C3=A4r
+
+
m=C3=A5n den 22 mars 2021 11:30am =E2=80=93 12:30pm +=C3=96steuropeisk tid - Helsingfors
+
+
Anslutningsinfo
+
+
Anslut till Google Meet
+
+ + +
+
Kalender
+
+
homer@example.com
+
+
Vem
+
+ + + + + + + + + + + +
+
=E2=80=A2
+
+
+
+
example@gmail.com + + + + +=E2=80=93 organisat=C3=B6r= +
+
+
+
+
=E2=80=A2
+
+
+
+
homer@example.com + +
+
+
+
+
+ +
+This is a test. Bold. Italic.
+
+Will discuss address for email <foo@example.com> and + +http://example.com?foo=3Dbar.
+ +
+
+

= +Ska du delta (homer@example.com)? + + +Ja + - + +Kanske + - + +Nej +fler + alternativ =C2=BB

+

+

Inbjudan fr=C3=A5n +Google Kalender

+

Detta e-postmeddelande har skickats till kontot homer@example.com efte= +rsom du =C3=A4r deltagare vid denna h=C3=A4ndelse.

+

Om du inte vill f=C3=A5 uppdateringar om denna h=C3=A4ndelse i framtiden= + kan du tacka nej till denna h=C3=A4ndelse. Du kan =C3=A4ven registrera dig= + f=C3=B6r att f=C3=A5 ett Google-konto p=C3=A5 https://calendar.google.com/= +calendar/ och kontrollera aviseringsinst=C3=A4llningarna f=C3=B6r hela kale= +ndern.

+

Om du vidarebefordrar den h=C3=A4r inbjudan kan det g=C3=B6ra det m=C3= +=B6jligt f=C3=B6r alla mottagare att skicka ett svar till organisat=C3=B6re= +n och l=C3=A4ggas till p=C3=A5 g=C3=A4stlistan, bjuda in andra oavsett dera= +s egen inbjudningsstatus eller modifiera ditt OSA. +L= +=C3=A4s mer.

+
+
+
+ + + +--0000000000008c6d6005be1c767c +Content-Type: text/calendar; charset="UTF-8"; method=REQUEST +Content-Transfer-Encoding: quoted-printable + +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +DTSTART:20210322T093000Z +DTEND:20210322T103000Z +DTSTAMP:20210322T091220Z +ORGANIZER;CN=3Dexample@gmail.com:mailto:example@gmail.com +UID:65m17hsdolmotv3kvmrtg40ont@google.com +ATTENDEE;CUTYPE=3DINDIVIDUAL;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DACCEPTED;RSV= +P=3DTRUE + ;CN=3Dexample@gmail.com;X-NUM-GUESTS=3D0:mailto:example@gmail.com +ATTENDEE;CUTYPE=3DINDIVIDUAL;ROLE=3DREQ-PARTICIPANT;PARTSTAT=3DNEEDS-ACTION= +;RSVP=3D + TRUE;CN=3Dhomer@example.com;X-NUM-GUESTS=3D0:mailto:homer@example.com +X-MICROSOFT-CDO-OWNERAPPTID:-410050292 +CREATED:20210322T091220Z +DESCRIPTION:This is a test. Bold. Italic. \;

Will= +=20 + discuss address for email <\;foo@example.com>\; and http://example.com= +? + foo=3Dbar.\n\n-::~:~::~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:= +~:~ + :~:~:~:~:~:~:~:~::~:~::-\n=C3=84ndra inte det h=C3=A4r avsnittet i beskriv= +ningen.\n\n + Den h=C3=A4r h=C3=A4ndelsen har ett videosamtal.\nG=C3=A5 med: https://mee= +t.google.com/pyb + -ndcu-hhc\n\nVisa din h=C3=A4ndelse p=C3=A5 https://calendar.google.com/ca= +lendar/even + t?action=3DVIEW&eid=3DNjVtMTdoc2RvbG1vdHYza3ZtcnRnNDBvbnQgbWFnbnVzLm1lbGlu= +QGh1d + C5maQ&tok=3DMjEjYmVydGF0aGVib3RAZ21haWwuY29tZTg2NGFjYmNjYWE1MjVlZWJmY2UzYm= +RmM + DAyNWU0MDkzNDAxZjRhZg&ctz=3DEurope%2FHelsinki&hl=3Dsv&es=3D1.\n-::~:~::~:~= +:~:~:~: + ~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~::~:~::- +LAST-MODIFIED:20210322T091220Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Meeeeet me HTML +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR + +--0000000000008c6d6005be1c767c-- + +--0000000000008c6d6205be1c767e +Content-Type: application/ics; name="invite.ics" +Content-Disposition: attachment; filename="invite.ics" +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSClBST0RJRDotLy9Hb29nbGUgSW5jLy9Hb29nbGUgQ2FsZW5kYXIgNzAu +OTA1NC8vRU4KVkVSU0lPTjoyLjAKQ0FMU0NBTEU6R1JFR09SSUFOCk1FVEhPRDpSRVFVRVNUCkJF +R0lOOlZFVkVOVApEVFNUQVJUOjIwMjEwMzIyVDA5MzAwMFoKRFRFTkQ6MjAyMTAzMjJUMTAzMDAw +WgpEVFNUQU1QOjIwMjEwMzIyVDA5MTIyMFoKT1JHQU5JWkVSO0NOPWV4YW1wbGVAZ21haWwuY29t +Om1haWx0bzpleGFtcGxlQGdtYWlsLmNvbQpVSUQ6NjVtMTdoc2RvbG1vdHYza3ZtcnRnNDBvbnRA +Z29vZ2xlLmNvbQpBVFRFTkRFRTtDVVRZUEU9SU5ESVZJRFVBTDtST0xFPVJFUS1QQVJUSUNJUEFO +VDtQQVJUU1RBVD1BQ0NFUFRFRDtSU1ZQPVRSVUUKIDtDTj1leGFtcGxlQGdtYWlsLmNvbTtYLU5V +TS1HVUVTVFM9MDptYWlsdG86ZXhhbXBsZUBnbWFpbC5jb20KQVRURU5ERUU7Q1VUWVBFPUlORElW +SURVQUw7Uk9MRT1SRVEtUEFSVElDSVBBTlQ7UEFSVFNUQVQ9TkVFRFMtQUNUSU9OO1JTVlA9CiBU +UlVFO0NOPWhvbWVyQGV4YW1wbGUuY29tO1gtTlVNLUdVRVNUUz0wOm1haWx0bzpob21lckBleGFt +cGxlLmNvbQpYLU1JQ1JPU09GVC1DRE8tT1dORVJBUFBUSUQ6LTQxMDA1MDI5MgpDUkVBVEVEOjIw +MjEwMzIyVDA5MTIyMFoKREVTQ1JJUFRJT046VGhpcyBpcyBhIHRlc3QuIDxiPkJvbGQ8L2I+LiA8 +aT5JdGFsaWM8L2k+LiZuYnNwXDs8YnI+PGJyPldpbGwgCiBkaXNjdXNzIGFkZHJlc3MgZm9yIGVt +YWlsICZsdFw7Zm9vQGV4YW1wbGUuY29tJmd0XDsgYW5kIGh0dHA6Ly9leGFtcGxlLmNvbT8KIGZv +bz1iYXIuXG5cbi06On46fjo6fjp+On46fjp+On46fjp+On46fjp+On46fjp+On46fjp+On46fjp+ +On46fjp+On46fjp+On46fgogOn46fjp+On46fjp+On46fjo6fjp+OjotXG7DhG5kcmEgaW50ZSBk +ZXQgaMOkciBhdnNuaXR0ZXQgaSBiZXNrcml2bmluZ2VuLlxuXG4KIERlbiBow6RyIGjDpG5kZWxz +ZW4gaGFyIGV0dCB2aWRlb3NhbXRhbC5cbkfDpSBtZWQ6IGh0dHBzOi8vbWVldC5nb29nbGUuY29t +L3B5YgogLW5kY3UtaGhjXG5cblZpc2EgZGluIGjDpG5kZWxzZSBww6UgaHR0cHM6Ly9jYWxlbmRh +ci5nb29nbGUuY29tL2NhbGVuZGFyL2V2ZW4KIHQ/YWN0aW9uPVZJRVcmZWlkPU5qVnRNVGRvYzJS +dmJHMXZkSFl6YTNadGNuUm5OREJ2Ym5RZ2JXRm5iblZ6TG0xbGJHbHVRR2gxZAogQzVtYVEmdG9r +PU1qRWpZbVZ5ZEdGMGFHVmliM1JBWjIxaGFXd3VZMjl0WlRnMk5HRmpZbU5qWVdFMU1qVmxaV0pt +WTJVelltUm1NCiBEQXlOV1UwTURrek5EQXhaalJoWmcmY3R6PUV1cm9wZSUyRkhlbHNpbmtpJmhs +PXN2JmVzPTEuXG4tOjp+On46On46fjp+On46fjoKIH46fjp+On46fjp+On46fjp+On46fjp+On46 +fjp+On46fjp+On46fjp+On46fjp+On46fjp+On46fjp+On46On46fjo6LQpMQVNULU1PRElGSUVE +OjIwMjEwMzIyVDA5MTIyMFoKTE9DQVRJT046ClNFUVVFTkNFOjAKU1RBVFVTOkNPTkZJUk1FRApT +VU1NQVJZOk1lZWVlZXQgbWUgSFRNTApUUkFOU1A6T1BBUVVFCkVORDpWRVZFTlQKRU5EOlZDQUxF +TkRBUgo= + +--0000000000008c6d6205be1c767e-- diff --git a/comm/calendar/test/browser/invitations/data/message-containing-event.eml b/comm/calendar/test/browser/invitations/data/message-containing-event.eml new file mode 100644 index 0000000000..d27c2976db --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/message-containing-event.eml @@ -0,0 +1,44 @@ +From: ExampleStore +Date: Wed, 24 Aug 2016 16:40:06 -0400 +Subject: ExampleStore - booking 01.09.2016 @ 09.25 - 09.50 +Content-Type: multipart/mixed; + boundary="_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35" +To: +Message-ID: +MIME-Version: 1.0 + +--_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35 +Content-Type: multipart/alternative; + boundary="_=ALT_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35" + +--_=ALT_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35 +Content-Type: text/plain; charset="UTF-8" + +Remember your booking @ 09.25 + +--_=ALT_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35 +Content-Type: text/html; charset="UTF-8" + + + +

You have a booking for 9.25

+ + + +--_=ALT_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35-- + +--_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35 +Content-Type: TeXt/CaLeNdAr; method=PUBLISH; charset=UTF-8 +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="booking.ics" + +QkVHSU46VkNBTEVOREFSDQpWRVJTSU9OOjIuMA0KTUVUSE9EOlBVQkxJU0gNClBST0RJRDpleGFt +cGxlLmNvbQ0KQkVHSU46VkVWRU5UDQpEVFNUQVJUOjIwMTYwOTAxVDA2MjUwMFoNCkRURU5EOjIw +MTYwOTAxVDA2NTAwMFoNCkRUU1RBTVA6MjAxNjA4MjRUMjA0MDAwWg0KVUlEOjIwMTYwODI0VDIw +NDAwMFotNTY4ODYwMjgwQGV4YW1wbGUuY29tDQpTVU1NQVJZOkhhaXJjdXQNCk9SR0FOSVpFUjpt +YWlsdG86c29tZW9uZUBleGFtcGxlLmNvbQ0KREVTQ1JJUFRJT046SGFpcmN1dCtzdHlsaW5nDQpM +T0NBVElPTjpTb21ld2hlcmUNClRSQU5TUDpPUEFRVUUNClNFUVVFTkNFOjANCkNMQVNTOlBVQkxJ +Qw0KQkVHSU46VkFMQVJNDQpUUklHR0VSOi1QVDYwTQ0KQUNUSU9OOkFVRElPDQpERVNDUklQVElP +TjpSZW1pbmRlcg0KRU5EOlZBTEFSTQ0KRU5EOlZFVkVOVA0KRU5EOlZDQUxFTkRBUg== + +--_=aspNetEmail=_51bed191ceac49f7a22392ea84b6ef35-- diff --git a/comm/calendar/test/browser/invitations/data/message-non-invite.eml b/comm/calendar/test/browser/invitations/data/message-non-invite.eml new file mode 100644 index 0000000000..cf391f445a --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/message-non-invite.eml @@ -0,0 +1,115 @@ +Date: Sun, 28 Nov 2021 21:39:31 +0000 +From: Jane +To: +Message-ID: <1074020157.32201638135571450.JavaMail.root@hki-example-prod-app-004> +Subject: We're having a party - you're NOT invited +Content-Type: multipart/mixed; + boundary="----=_Part_6440_2094089067.1638135571440" +MIME-Version: 1.0 + +------=_Part_6440_2094089067.1638135571440 +Content-Type: multipart/related; + boundary="----=_Part_6441_499243807.1638135571440" + +------=_Part_6441_499243807.1638135571440 +Content-Type: text/plain; charset="UTF-8" + +Hey, we're having a party! You're not invited ;) + +------=_Part_6441_499243807.1638135571440-- + +------=_Part_6440_2094089067.1638135571440 +Content-Type: text/calendar; charset="utf-8"; name="event.ics" +Content-Transfer-Encoding: quoted-printable +Content-Disposition: attachment; filename="event.ics" + +BEGIN:VCALENDAR +PRODID:-//EXAMPLE:COM//iCal4j 1.0.5.2//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VTIMEZONE +TZID:Europe/Helsinki +TZURL:http://tzurl.org/zoneinfo/Europe/Helsinki +X-LIC-LOCATION:Europe/Helsinki +BEGIN:DAYLIGHT +TZOFFSETFROM:+0200 +TZOFFSETTO:+0300 +TZNAME:EEST +DTSTART:19830327T030000 +RRULE:FREQ=3DYEARLY;BYMONTH=3D3;BYDAY=3D-1SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0300 +TZOFFSETTO:+0200 +TZNAME:EET +DTSTART:19961027T040000 +RRULE:FREQ=3DYEARLY;BYMONTH=3D10;BYDAY=3D-1SU +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+013952 +TZOFFSETTO:+013952 +TZNAME:HMT +DTSTART:18780531T000000 +RDATE:18780531T000000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+013952 +TZOFFSETTO:+0200 +TZNAME:EET +DTSTART:19210501T000000 +RDATE:19210501T000000 +END:STANDARD +BEGIN:DAYLIGHT +TZOFFSETFROM:+0200 +TZOFFSETTO:+0300 +TZNAME:EEST +DTSTART:19420403T000000 +RDATE:19420403T000000 +RDATE:19810329T020000 +RDATE:19820328T020000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0300 +TZOFFSETTO:+0200 +TZNAME:EET +DTSTART:19421003T000000 +RDATE:19421003T000000 +RDATE:19810927T030000 +RDATE:19820926T030000 +RDATE:19830925T040000 +RDATE:19840930T040000 +RDATE:19850929T040000 +RDATE:19860928T040000 +RDATE:19870927T040000 +RDATE:19880925T040000 +RDATE:19890924T040000 +RDATE:19900930T040000 +RDATE:19910929T040000 +RDATE:19920927T040000 +RDATE:19930926T040000 +RDATE:19940925T040000 +RDATE:19950924T040000 +END:STANDARD +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0200 +TZNAME:EET +DTSTART:19830101T000000 +RDATE:19830101T000000 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20211128T213931Z +DTSTART;TZID=3DEurope/Helsinki:20211129T105500 +DTEND;TZID=3DEurope/Helsinki:20211129T110000 +SUMMARY:Party at John's house\, Helsinki +ORGANIZER;CN=3DJANE:mailto:noreply@example.com +UID:1e5fd4e6-bc52-439c-ac76-40da54f57c77@secure.example.com +SEQUENCE:3 +STATUS:CONFIRMED +LAST-MODIFIED:20211128T213931Z +END:VEVENT +END:VCALENDAR + +------=_Part_6440_2094089067.1638135571440-- diff --git a/comm/calendar/test/browser/invitations/data/outlook-test-invite.eml b/comm/calendar/test/browser/invitations/data/outlook-test-invite.eml new file mode 100644 index 0000000000..de07a9b873 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/outlook-test-invite.eml @@ -0,0 +1,102 @@ +From: Marge +To: Homer +Subject: Testaus +Thread-Topic: Testaus +Thread-Index: AdfdgAAehFDfsPyXTommZqRYgeMqiQAABo4Q +Date: Fri, 19 Nov 2021 20:00:31 +0000 +Message-ID: +Accept-Language: en-US +Content-Language: en-US +Content-Type: multipart/alternative; + boundary="_000_HE1P190MB0540579ABA18FCE320901B31E39C9HE1P190MB0540EURP_" +MIME-Version: 1.0 + +--_000_HE1P190MB0540579ABA18FCE320901B31E39C9HE1P190MB0540EURP_ +Content-Type: text/plain; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + + + +--_000_HE1P190MB0540579ABA18FCE320901B31E39C9HE1P190MB0540EURP_ +Content-Type: text/html; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + + + + + + + +
+

 

+
+ + + +--_000_HE1P190MB0540579ABA18FCE320901B31E39C9HE1P190MB0540EURP_ +Content-Type: text/calendar; charset="utf-8"; method=REQUEST +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSCk1FVEhPRDpSRVFVRVNUClBST0RJRDpNaWNyb3NvZnQgRXhjaGFuZ2Ug +U2VydmVyIDIwMTAKVkVSU0lPTjoyLjAKQkVHSU46VlRJTUVaT05FClRaSUQ6RkxFIFN0YW5kYXJk +IFRpbWUKQkVHSU46U1RBTkRBUkQKRFRTVEFSVDoxNjAxMDEwMVQwNDAwMDAKVFpPRkZTRVRGUk9N +OiswMzAwClRaT0ZGU0VUVE86KzAyMDAKUlJVTEU6RlJFUT1ZRUFSTFk7SU5URVJWQUw9MTtCWURB +WT0tMVNVO0JZTU9OVEg9MTAKRU5EOlNUQU5EQVJECkJFR0lOOkRBWUxJR0hUCkRUU1RBUlQ6MTYw +MTAxMDFUMDMwMDAwClRaT0ZGU0VURlJPTTorMDIwMApUWk9GRlNFVFRPOiswMzAwClJSVUxFOkZS +RVE9WUVBUkxZO0lOVEVSVkFMPTE7QllEQVk9LTFTVTtCWU1PTlRIPTMKRU5EOkRBWUxJR0hUCkVO +RDpWVElNRVpPTkUKQkVHSU46VkVWRU5UCk9SR0FOSVpFUjtDTj1NYXJnZTptYWlsdG86bWFyZ2VA +ZXhhbXBsZS5vcmcKQVRURU5ERUU7Uk9MRT1SRVEtUEFSVElDSVBBTlQ7UEFSVFNUQVQ9TkVFRFMt +QUNUSU9OO1JTVlA9VFJVRTtDTj1Ib20KIGVyOm1haWx0bzpob21lckBleGFtcGxlLm9yZwpERVND +UklQVElPTjtMQU5HVUFHRT1lbi1VUzpcbgpVSUQ6MDMwMDAwMDA4MjAwRTAwMDc0QzVCNzEwMUE4 +MkUwMDgwMDAwMDAwMDYwQjYwOEQwOTBEREQ3MDEwMDAwMDAwMDAwMDAwMDAKIDAxMDAwMDAwMDRC +RTBDRkZBNTRCQ0Y2NEU5NTZFMzQxNDMzNjJDM0MwClNVTU1BUlk7TEFOR1VBR0U9ZW4tVVM6VGVz +dGF1cwpEVFNUQVJUO1RaSUQ9RkxFIFN0YW5kYXJkIFRpbWU6MjAyMTExMjdUMDkwMDAwCkRURU5E +O1RaSUQ9RkxFIFN0YW5kYXJkIFRpbWU6MjAyMTExMjdUMDkzMDAwCkNMQVNTOlBVQkxJQwpQUklP +UklUWTo1CkRUU1RBTVA6MjAyMTExMTlUMjAwMDI5WgpUUkFOU1A6T1BBUVVFClNUQVRVUzpDT05G +SVJNRUQKU0VRVUVOQ0U6MApMT0NBVElPTjtMQU5HVUFHRT1lbi1VUzoKWC1NSUNST1NPRlQtQ0RP +LUFQUFQtU0VRVUVOQ0U6MApYLU1JQ1JPU09GVC1DRE8tT1dORVJBUFBUSUQ6LTcyMDEyODAyNwpY +LU1JQ1JPU09GVC1DRE8tQlVTWVNUQVRVUzpURU5UQVRJVkUKWC1NSUNST1NPRlQtQ0RPLUlOVEVO +REVEU1RBVFVTOkJVU1kKWC1NSUNST1NPRlQtQ0RPLUFMTERBWUVWRU5UOkZBTFNFClgtTUlDUk9T +T0ZULUNETy1JTVBPUlRBTkNFOjEKWC1NSUNST1NPRlQtQ0RPLUlOU1RUWVBFOjAKWC1NSUNST1NP +RlQtRE9OT1RGT1JXQVJETUVFVElORzpGQUxTRQpYLU1JQ1JPU09GVC1ESVNBTExPVy1DT1VOVEVS +OkZBTFNFClgtTUlDUk9TT0ZULUxPQ0FUSU9OUzpbXQpCRUdJTjpWQUxBUk0KREVTQ1JJUFRJT046 +UkVNSU5ERVIKVFJJR0dFUjtSRUxBVEVEPVNUQVJUOi1QVDE1TQpBQ1RJT046RElTUExBWQpFTkQ6 +VkFMQVJNCkVORDpWRVZFTlQKRU5EOlZDQUxFTkRBUgo= + +--_000_HE1P190MB0540579ABA18FCE320901B31E39C9HE1P190MB0540EURP_-- diff --git a/comm/calendar/test/browser/invitations/data/repeat-event.eml b/comm/calendar/test/browser/invitations/data/repeat-event.eml new file mode 100644 index 0000000000..9247e6575b --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/repeat-event.eml @@ -0,0 +1,49 @@ +MIME-Version: 1.0 +Date: Mon, 28 Mar 2022 17:49:35 +0000 +Subject: Invitation: Repeat Event @ Daily from 2pm to 3pm 3 times (AST) (receiver@example.com) +From: sender@example.com +To: receiver@example.com +Content-Type: multipart/mixed; boundary="00000000000080f3db05db4aef5b" + +--00000000000080f3db05db4aef5b +Content-Type: multipart/alternative; boundary="00000000000080f3da05db4aef59" + +--00000000000080f3da05db4aef59 +Content-Type: text/calendar; charset="UTF-8"; method=REQUEST +Content-Transfer-Encoding: 7bit + +BEGIN:VCALENDAR +METHOD:REQUEST +BEGIN:VEVENT +DTSTART:20220316T110000Z +DTEND:20220316T113000Z +RRULE:FREQ=DAILY;WKST=SU;COUNT=3;INTERVAL=1 +DTSTAMP:20220316T191602Z +UID:02e79b96 +ORGANIZER;CN=Sender; + EMAIL=sender@example.com:mailto:sender@example.com +ATTENDEE;CN=Sender; + EMAIL=sender@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=receiver@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:other@example.com +CREATED:20220328T174934Z +LAST-MODIFIED:20220328T174934Z +LOCATION:Somewhere +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Repeat Event +DESCRIPTION:An event invitation. +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR + +--00000000000080f3da05db4aef59-- +--00000000000080f3db05db4aef5b-- diff --git a/comm/calendar/test/browser/invitations/data/repeat-update-major.eml b/comm/calendar/test/browser/invitations/data/repeat-update-major.eml new file mode 100644 index 0000000000..61fe9f5022 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/repeat-update-major.eml @@ -0,0 +1,49 @@ +MIME-Version: 1.0 +Date: Mon, 28 Mar 2022 17:49:35 +0000 +Subject: Repeat Update Major +From: sender@example.com +To: receiver@example.com +Content-Type: multipart/mixed; boundary="00000000000080f3db05db4aef5b" + +--00000000000080f3db05db4aef5b +Content-Type: multipart/alternative; boundary="00000000000080f3da05db4aef59" + +--00000000000080f3da05db4aef59 +Content-Type: text/calendar; charset="UTF-8"; method=REQUEST +Content-Transfer-Encoding: 7bit + +BEGIN:VCALENDAR +METHOD:REQUEST +BEGIN:VEVENT +DTSTART:20220316T050000Z +DTEND:20220316T053000Z +RRULE:FREQ=DAILY;WKST=SU;COUNT=3;INTERVAL=1 +DTSTAMP:20220316T191602Z +UID:02e79b96 +ORGANIZER;CN=Sender; + EMAIL=sender@example.com:mailto:sender@example.com +ATTENDEE;CN=Sender; + EMAIL=sender@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=receiver@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:other@example.com +CREATED:20220328T174934Z +LAST-MODIFIED:20220328T174934Z +LOCATION:Somewhere +SEQUENCE:2 +STATUS:CONFIRMED +SUMMARY:Repeat Event +DESCRIPTION:An event invitation. +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR + +--00000000000080f3da05db4aef59-- +--00000000000080f3db05db4aef5b-- diff --git a/comm/calendar/test/browser/invitations/data/repeat-update-minor.eml b/comm/calendar/test/browser/invitations/data/repeat-update-minor.eml new file mode 100644 index 0000000000..a6ad357553 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/repeat-update-minor.eml @@ -0,0 +1,49 @@ +MIME-Version: 1.0 +Date: Mon, 28 Mar 2022 17:49:35 +0000 +Subject: Invitation: Repeat Update Minor +From: sender@example.com +To: receiver@example.com +Content-Type: multipart/mixed; boundary="00000000000080f3db05db4aef5b" + +--00000000000080f3db05db4aef5b +Content-Type: multipart/alternative; boundary="00000000000080f3da05db4aef59" + +--00000000000080f3da05db4aef59 +Content-Type: text/calendar; charset="UTF-8"; method=REQUEST +Content-Transfer-Encoding: 7bit + +BEGIN:VCALENDAR +METHOD:REQUEST +BEGIN:VEVENT +DTSTART:20220316T110000Z +DTEND:20220316T113000Z +RRULE:FREQ=DAILY;WKST=SU;COUNT=3;INTERVAL=1 +DTSTAMP:20220318T191602Z +UID:02e79b96 +ORGANIZER;CN=Sender; + EMAIL=sender@example.com:mailto:sender@example.com +ATTENDEE;CN=Sender; + EMAIL=sender@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=receiver@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=INDIVIDUAL; + PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:other@example.com +CREATED:20220328T174934Z +LAST-MODIFIED:20220328T174934Z +LOCATION:Updated location +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Updated Event +DESCRIPTION:Updated description. +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:Updated description. +END:VALARM +END:VEVENT +END:VCALENDAR + +--00000000000080f3da05db4aef59-- +--00000000000080f3db05db4aef5b-- diff --git a/comm/calendar/test/browser/invitations/data/single-event.eml b/comm/calendar/test/browser/invitations/data/single-event.eml new file mode 100644 index 0000000000..14418c2c79 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/single-event.eml @@ -0,0 +1,78 @@ +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/mixed; boundary="_----------=_1647458162153312762582" +Date: Wed, 16 Mar 2022 15:16:02 -0400 +To: receiver@example.com +Subject: Invitation: Single Event @ Wed, Mar 16 2022 11:00 AST +From: Sender + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762582 +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/alternative; boundary="_----------=_1647458162153312762583" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Length: 227 +Content-Transfer-Encoding: binary +Content-Type: text/plain; charset="utf-8" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +Single Event + +When: + Wed, Mar 16 2022 + 11:00 - 12:00 AST +Where: + Somewhere + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Transfer-Encoding: quoted-printable +Content-Type: text/calendar; charset="utf-8"; method="REQUEST" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REQUEST +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:02e79b96 +SEQUENCE:0 +DTSTAMP:20220316T191602Z +CREATED:20220316T191532Z +DTSTART:20220316T110000Z +DTEND:20220316T113000Z +DURATION:PT1H +PRIORITY:0 +SUMMARY:Single Event +DESCRIPTION:An event invitation. +LOCATION:Somewhere +STATUS:CONFIRMED +TRANSP:OPAQUE +CLASS:PUBLIC +ORGANIZER;CN=3DSender; + EMAIL=3Dsender@example.com:mailto:sender@example.com +ATTENDEE;CN=3DSender; + EMAIL=3Dsender@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DACCEPTED;RSVP=3DFALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=3Dreceiver@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:other@example.com +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR + +--_----------=_1647458162153312762583-- diff --git a/comm/calendar/test/browser/invitations/data/teams-meeting-invite.eml b/comm/calendar/test/browser/invitations/data/teams-meeting-invite.eml new file mode 100644 index 0000000000..777486ec87 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/teams-meeting-invite.eml @@ -0,0 +1,167 @@ +From: Marge +To: bart@example.com, homer@example.com +Subject: Teams meeting +Thread-Topic: Teams meeting +Thread-Index: AdbIy2RnFnEYrGmq80aB3RiaEcOS6w== +Date: Wed, 2 Dec 2020 16:52:34 +0000 +Message-ID: +Accept-Language: fi-FI, en-US +Content-Language: en-US +X-MS-Has-Attach: +X-MS-TNEF-Correlator: +Content-Type: multipart/alternative; + boundary="_000_HE1PR0802MB228346BE1576FEAB8A7F32328EF30HE1PR0802MB2283_" +X-Spam-Flag: No +Return-Path: marge@example.com +MIME-Version: 1.0 + +--_000_HE1PR0802MB228346BE1576FEAB8A7F32328EF30HE1PR0802MB2283_ +Content-Type: text/plain; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + + + + +___________________________________________________________________________= +_____ +Microsoft Teams -kokous +Liity tietokoneella tai mobiilisovelluksella +Liity kokoukseen napsauttamalla t=E4t=E4 +Lis=E4tietoja | Kokousasetukset +___________________________________________________________________________= +_____ + +--_000_HE1PR0802MB228346BE1576FEAB8A7F32328EF30HE1PR0802MB2283_ +Content-Type: text/html; charset="iso-8859-1" +Content-Transfer-Encoding: quoted-printable + + + + + + +

+
+
+
________________________________________________= +________________________________ +
+
+
Microsoft Teams -kokous +
+
+
Liity tietokoneella tai mobiiliso= +velluksella +
+Liity + kokoukseen napsauttamalla t=E4t=E4
+ +
+
+
+
+
________________________________________________= +________________________________ +
+
+ + + +--_000_HE1PR0802MB228346BE1576FEAB8A7F32328EF30HE1PR0802MB2283_ +Content-Type: text/calendar; charset="utf-8"; method=REQUEST +Content-Transfer-Encoding: base64 + +QkVHSU46VkNBTEVOREFSCk1FVEhPRDpSRVFVRVNUClBST0RJRDpNaWNyb3NvZnQgRXhjaGFuZ2Ug +U2VydmVyIDIwMTAKVkVSU0lPTjoyLjAKQkVHSU46VlRJTUVaT05FClRaSUQ6RkxFIFN0YW5kYXJk +IFRpbWUKQkVHSU46U1RBTkRBUkQKRFRTVEFSVDoxNjAxMDEwMVQwNDAwMDAKVFpPRkZTRVRGUk9N +OiswMzAwClRaT0ZGU0VUVE86KzAyMDAKUlJVTEU6RlJFUT1ZRUFSTFk7SU5URVJWQUw9MTtCWURB +WT0tMVNVO0JZTU9OVEg9MTAKRU5EOlNUQU5EQVJECkJFR0lOOkRBWUxJR0hUCkRUU1RBUlQ6MTYw +MTAxMDFUMDMwMDAwClRaT0ZGU0VURlJPTTorMDIwMApUWk9GRlNFVFRPOiswMzAwClJSVUxFOkZS +RVE9WUVBUkxZO0lOVEVSVkFMPTE7QllEQVk9LTFTVTtCWU1PTlRIPTMKRU5EOkRBWUxJR0hUCkVO +RDpWVElNRVpPTkUKQkVHSU46VkVWRU5UCk9SR0FOSVpFUjtDTj1NYXJnZTptYWlsdG86bWFyZ2VA +ZXhhbXBsZS5jb20KQVRURU5ERUU7Uk9MRT1SRVEtUEFSVElDSVBBTlQ7UEFSVFNUQVQ9TkVFRFMt +QUNUSU9OO1JTVlA9VFJVRTtDTj1iYXJ0QGUKIGV4YW1wbGUuY29tOm1haWx0bzpiYXJ0QGV4YW1w +bGUuY29tCkFUVEVOREVFO1JPTEU9UkVRLVBBUlRJQ0lQQU5UO1BBUlRTVEFUPU5FRURTLUFDVElP +TjtSU1ZQPVRSVUU7Q049aG9tZXJAZXgKIGFtcGxlLmNvbTptYWlsdG86aG9tZXJAZXhhbXBsZS5j +b20KCkRFU0NSSVBUSU9OO0xBTkdVQUdFPWVuLVVTOlxuXG5cbl9fX19fX19fX19fX19fX19fX19f +X19fX19fX19fX19fX19fX19fX19fXwogX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19f +X19fX19cbk1pY3Jvc29mdCBUZWFtcyAta29rb3VzXG5MaWl0eSB0aWUKIHRva29uZWVsbGEgdGFp +IG1vYmlpbGlzb3ZlbGx1a3NlbGxhXG5MaWl0eSBrb2tvdWtzZWVuIG5hcHNhdXR0YW1hbGxhIHTD +pHQKIMOkPGh0dHBzOi8vdGVhbXMubWljcm9zb2Z0LmNvbS9sL21lZXR1cC1qb2luLzE5JTNhbWVl +dGluZ19NR1U1Tm1JMlpHWXRPV1ptCiBPQzAwWTJabUxXSmxPVEl0TmpVeE5qQTVZalV5WVRZeSU0 +MHRocmVhZC52Mi8wP2NvbnRleHQ9JTdiJTIyVGlkJTIyJTNhJTIyMgogZmQwYzFjNS0yOGUxLTQw +YzQtOWYwZC1hMDM2M2NhODBhM2MlMjIlMmMlMjJPaWQlMjIlM2ElMjIxNDQ2NGQwOS1jZWI4LTQ1 +OGMKIC1hNjFjLTcxN2YxZTVjNjZjNSUyMiU3ZD5cbkxpc8OkdGlldG9qYTxodHRwczovL2FrYS5t +cy9Kb2luVGVhbXNNZWV0aW5nPiB8CiAgS29rb3VzYXNldHVrc2V0PGh0dHBzOi8vdGVhbXMubWlj +cm9zb2Z0LmNvbS9tZWV0aW5nT3B0aW9ucy8/b3JnYW5pemVySWQ9MQogNDQ2NGQwOS1jZWI4LTQ1 +OGMtYTYxYy03MTdmMWU1YzY2YzUmdGVuYW50SWQ9MmZkMGMxYzUtMjhlMS00MGM0LTlmMGQtYTAz +NjMKIGNhODBhM2MmdGhyZWFkSWQ9MTlfbWVldGluZ19NR1U1Tm1JMlpHWXRPV1ptT0MwMFkyWm1M +V0psT1RJdE5qVXhOakE1WWpVeVlUCiBZeUB0aHJlYWQudjImbWVzc2FnZUlkPTAmbGFuZ3VhZ2U9 +ZmktRkk+XG5fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXwogX19fX19fX19fX19fX19f +X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fXG4KVUlEOjA1NjAwMDAwODIwMEUwMDA3 +NEM1QjcxMDFBODJFMDA4MDAwMDAwMDAxQUY4NEM2NENCQzhENjAxMDAwMDAwMDAwMDAwMDAwCiAw +MTAwMDAwMDA0MDNCNUFDMTBBMEVCNDQ0QTk0N0QyQjQ5OUE0Qjk4QwpTVU1NQVJZO0xBTkdVQUdF +PWVuLVVTOlRlYW1zIG1lZXRpbmcKRFRTVEFSVDtUWklEPUZMRSBTdGFuZGFyZCBUaW1lOjIwMjAx +MjAyVDE5MDAwMApEVEVORDtUWklEPUZMRSBTdGFuZGFyZCBUaW1lOjIwMjAxMjAyVDIxMDAwMApD +TEFTUzpQVUJMSUMKUFJJT1JJVFk6NQpEVFNUQU1QOjIwMjAxMjAyVDE2NTEzNFoKVFJBTlNQOk9Q +QVFVRQpTVEFUVVM6Q09ORklSTUVEClNFUVVFTkNFOjAKTE9DQVRJT047TEFOR1VBR0U9ZW4tVVM6 +ClgtTUlDUk9TT0ZULUNETy1BUFBULVNFUVVFTkNFOjAKWC1NSUNST1NPRlQtQ0RPLU9XTkVSQVBQ +VElEOjIxMTg5MTUzNTQKWC1NSUNST1NPRlQtQ0RPLUJVU1lTVEFUVVM6VEVOVEFUSVZFClgtTUlD +Uk9TT0ZULUNETy1JTlRFTkRFRFNUQVRVUzpCVVNZClgtTUlDUk9TT0ZULUNETy1BTExEQVlFVkVO +VDpGQUxTRQpYLU1JQ1JPU09GVC1DRE8tSU1QT1JUQU5DRToxClgtTUlDUk9TT0ZULUNETy1JTlNU +VFlQRTowClgtTUlDUk9TT0ZULVNLWVBFVEVBTVNNRUVUSU5HVVJMOmh0dHBzOi8vdGVhbXMubWlj +cm9zb2Z0LmNvbS9sL21lZXR1cC1qb2luLwogMTklM2FtZWV0aW5nX01HVTVObUkyWkdZdE9XWm1P +QzAwWTJabUxXSmxPVEl0TmpVeE5qQTVZalV5WVRZeSU0MHRocmVhZC52Mi8KIDA/Y29udGV4dD0l +N2IlMjJUaWQlMjIlM2ElMjIyZmQwYzFjNS0xOGUxLTQwYzQtOWYwZC1hMDM2M2NhODBhM2MlMjIl +MmMlMjJPCiBpZCUyMiUzYSUyMjE0NDY0ZDA5LWNlYjgtNDU4Yy1hNjFjLTcxN2YxZTVjNjZjNSUy +MiU3ZApYLU1JQ1JPU09GVC1TQ0hFRFVMSU5HU0VSVklDRVVQREFURVVSTDpodHRwczovL3NjaGVk +dWxlci50ZWFtcy5taWNyb3NvZnQuY28KIG0vdGVhbXMvMmZkMGMxYzUtMjhlMS00MGM0LTlmMGQt +YWIzNjNjYTgwYTNjLzE0NDY0ZDA5LWNlYjgtNDU4Yy1hNjFjLTcxN2YxCiBlNWM2NmM1LzE5X21l +ZXRpbmdfTUdVNU5tSTJaR1l0T1dabU9DMDBZMlptTFdKbE9USXROalV4TmpBNVlqVXlZVFl5QHRo +cmVhZAogLnYyLzAKWC1NSUNST1NPRlQtU0tZUEVURUFNU1BST1BFUlRJRVM6eyJjaWQiOiIxOTpt +ZWV0aW5nX01HVTVObUkyWkdZdE9XWm1PQzAwWTJaCiBtTFdKbE9USXROalV4TmpBNVlqVXlZVFl5 +QHRocmVhZC52MiJcLCJyaWQiOjBcLCJtaWQiOjBcLCJ1aWQiOm51bGxcLCJwcml2YQogdGUiOnRy +dWVcLCJ0eXBlIjowfQpYLU1JQ1JPU09GVC1PTkxJTkVNRUVUSU5HQ09ORkxJTks6Y29uZjpzaXA6 +bWFyZ2VAZXhhbXBsZS5jb21cO2dydXVcO29wCiBhcXVlPWFwcDpjb25mOmZvY3VzOmlkOnRlYW1z +OjI6MCExOTptZWV0aW5nX01HVTVNbUkyWkdZdE9XWm1PQzAwWTJabUxXSmxPVAogSXROalV4TmpB +NVlqVXlZVFl5LXRocmVhZC52MiExNDQ2NGQwOWNlYjg0NThjYTYxYzcxN2YxZTVjNjZjNSEyZmQw +YzFjNTI4ZTEKIDQwYzQ5ZjBkYTAzNjNjYTgwYTNjClgtTUlDUk9TT0ZULU9OTElORU1FRVRJTkdJ +TkZPUk1BVElPTjp7Ik9ubGluZU1lZXRpbmdDaGFubmVsSWQiOm51bGxcLCJPbmxpbgogZU1lZXRp +bmdQcm92aWRlciI6M30KWC1NSUNST1NPRlQtRE9OT1RGT1JXQVJETUVFVElORzpGQUxTRQpYLU1J +Q1JPU09GVC1ESVNBTExPVy1DT1VOVEVSOkZBTFNFClgtTUlDUk9TT0ZULUxPQ0FUSU9OUzpbXQpC +RUdJTjpWQUxBUk0KREVTQ1JJUFRJT046UkVNSU5ERVIKVFJJR0dFUjtSRUxBVEVEPVNUQVJUOi1Q +VDE1TQpBQ1RJT046RElTUExBWQpFTkQ6VkFMQVJNCkVORDpWRVZFTlQKRU5EOlZDQUxFTkRBUgo= + +--_000_HE1PR0802MB228346BE1576FEAB8A7F32328EF30HE1PR0802MB2283_-- diff --git a/comm/calendar/test/browser/invitations/data/update-major.eml b/comm/calendar/test/browser/invitations/data/update-major.eml new file mode 100644 index 0000000000..04754798b2 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/update-major.eml @@ -0,0 +1,78 @@ +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/mixed; boundary="_----------=_1647458162153312762582" +Date: Wed, 16 Mar 2022 15:16:02 -0400 +To: receiver@example.com +Subject: Update Major +From: Sender + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762582 +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/alternative; boundary="_----------=_1647458162153312762583" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Length: 227 +Content-Transfer-Encoding: binary +Content-Type: text/plain; charset="utf-8" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +Single Event + +When: + Wed, Mar 16 2022 + 11:00 - 12:00 AST +Where: + Somewhere + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Transfer-Encoding: quoted-printable +Content-Type: text/calendar; charset="utf-8"; method="REQUEST" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REQUEST +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:02e79b96 +SEQUENCE:2 +DTSTAMP:20220316T191602Z +CREATED:20220316T191532Z +DTSTART:20220316T050000Z +DTEND:20220316T053000Z +DURATION:PT1H +PRIORITY:0 +SUMMARY:Single Event +DESCRIPTION:An event invitation. +LOCATION:Somewhere +STATUS:CONFIRMED +TRANSP:OPAQUE +CLASS:PUBLIC +ORGANIZER;CN=3DSender; + EMAIL=3Dsender@example.com:mailto:sender@example.com +ATTENDEE;CN=3DSender; + EMAIL=3Dsender@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DACCEPTED;RSVP=3DFALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=3Dreceiver@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:other@example.com +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:This is an event reminder +END:VALARM +END:VEVENT +END:VCALENDAR + +--_----------=_1647458162153312762583-- diff --git a/comm/calendar/test/browser/invitations/data/update-minor.eml b/comm/calendar/test/browser/invitations/data/update-minor.eml new file mode 100644 index 0000000000..afeb8e9ba0 --- /dev/null +++ b/comm/calendar/test/browser/invitations/data/update-minor.eml @@ -0,0 +1,78 @@ +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/mixed; boundary="_----------=_1647458162153312762582" +Date: Wed, 16 Mar 2022 15:16:02 -0400 +To: receiver@example.com +Subject: Update Minor +From: Sender + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762582 +MIME-Version: 1.0 +Content-Transfer-Encoding: binary +Content-Type: multipart/alternative; boundary="_----------=_1647458162153312762583" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +This is a multi-part message in MIME format. + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Length: 227 +Content-Transfer-Encoding: binary +Content-Type: text/plain; charset="utf-8" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +Single Event + +When: + Wed, Mar 16 2022 + 11:00 - 12:00 AST +Where: + Somewhere + +--_----------=_1647458162153312762583 +MIME-Version: 1.0 +Content-Disposition: inline +Content-Transfer-Encoding: quoted-printable +Content-Type: text/calendar; charset="utf-8"; method="REQUEST" +Date: Wed, 16 Mar 2022 15:16:02 -0400 + +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REQUEST +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:02e79b96 +SEQUENCE:0 +DTSTAMP:20220318T191602Z +CREATED:20220316T191532Z +DTSTART:20220316T110000Z +DTEND:20220316T113000Z +DURATION:PT1H +PRIORITY:0 +SUMMARY:Updated Event +DESCRIPTION:Updated description. +LOCATION:Updated location +STATUS:CONFIRMED +TRANSP:OPAQUE +CLASS:PUBLIC +ORGANIZER;CN=3DSender; + EMAIL=3Dsender@example.com:mailto:sender@example.com +ATTENDEE;CN=3DSender; + EMAIL=3Dsender@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DACCEPTED;RSVP=3DFALSE:mailto:sender@example.com +ATTENDEE;CN=Receiver;EMAIL=3Dreceiver@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:receiver@example.com +ATTENDEE;CN=Other;EMAIL=other@example.com;CUTYPE=3DINDIVIDUAL; + PARTSTAT=3DNEEDS-ACTION;RSVP=3DTRUE:mailto:other@example.com +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER:-P1D +DESCRIPTION:Updated description. +END:VALARM +END:VEVENT +END:VCALENDAR + +--_----------=_1647458162153312762583-- diff --git a/comm/calendar/test/browser/invitations/head.js b/comm/calendar/test/browser/invitations/head.js new file mode 100644 index 0000000000..24835c3021 --- /dev/null +++ b/comm/calendar/test/browser/invitations/head.js @@ -0,0 +1,942 @@ +/* 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/. */ + +/** + * Common functions for the imip-bar tests. + * + * Note that these tests are heavily tied to the .eml files found in the data + * folder. + */ + +"use strict"; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { CalItipDefaultEmailTransport } = ChromeUtils.import( + "resource:///modules/CalItipEmailTransport.jsm" +); +var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm"); + +var { FileTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/FileTestUtils.sys.mjs" +); +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); + +registerCleanupFunction(async () => { + // Some tests that open new windows don't return focus to the main window + // in a way that satisfies mochitest, and the test times out. + Services.focus.focusedWindow = window; + document.body.focus(); +}); + +class EmailTransport extends CalItipDefaultEmailTransport { + sentItems = []; + + sentMsgs = []; + + getMsgSend() { + let { sentMsgs } = this; + return { + sendMessageFile( + userIdentity, + accountKey, + composeFields, + messageFile, + deleteSendFileOnCompletion, + digest, + deliverMode, + msgToReplace, + listener, + statusFeedback, + smtpPassword + ) { + sentMsgs.push({ + userIdentity, + accountKey, + composeFields, + messageFile, + deleteSendFileOnCompletion, + digest, + deliverMode, + msgToReplace, + listener, + statusFeedback, + smtpPassword, + }); + }, + }; + } + + sendItems(recipients, itipItem, fromAttendee) { + this.sentItems.push({ recipients, itipItem, fromAttendee }); + return super.sendItems(recipients, itipItem, fromAttendee); + } + + reset() { + this.sentItems = []; + this.sentMsgs = []; + } +} + +async function openMessageFromFile(file) { + let fileURL = Services.io + .newFileURI(file) + .mutate() + .setQuery("type=application/x-message-display") + .finalize(); + + let winPromise = BrowserTestUtils.domWindowOpenedAndLoaded(); + window.openDialog( + "chrome://messenger/content/messageWindow.xhtml", + "_blank", + "all,chrome,dialog=no,status,toolbar", + fileURL + ); + let win = await winPromise; + await BrowserTestUtils.waitForEvent(win, "MsgLoaded"); + await TestUtils.waitForCondition(() => Services.focus.activeWindow == win); + return win; +} + +/** + * Opens an iMIP message file and waits for the imip-bar to appear. + * + * @param {nsIFile} file + * @returns {Window} + */ +async function openImipMessage(file) { + let win = await openMessageFromFile(file); + let aboutMessage = win.document.getElementById("messageBrowser").contentWindow; + let imipBar = aboutMessage.document.getElementById("imip-bar"); + await TestUtils.waitForCondition(() => !imipBar.collapsed, "imip-bar shown"); + + if (Services.prefs.getBoolPref("calendar.itip.newInvitationDisplay")) { + // CalInvitationDisplay.show() does some async activities before the panel is added. + await TestUtils.waitForCondition( + () => + win.document + .getElementById("messageBrowser") + .contentDocument.querySelector("calendar-invitation-panel"), + "calendar-invitation-panel shown" + ); + } + return win; +} + +/** + * Clicks on one of the imip-bar action buttons. + * + * @param {Window} win + * @param {string} id + */ +async function clickAction(win, id) { + let aboutMessage = win.document.getElementById("messageBrowser").contentWindow; + let action = aboutMessage.document.getElementById(id); + await TestUtils.waitForCondition(() => !action.hidden, `button "#${id}" shown`); + + EventUtils.synthesizeMouseAtCenter(action, {}, aboutMessage); + await TestUtils.waitForCondition(() => action.hidden, `button "#${id}" hidden`); +} + +/** + * Clicks on one of the imip-bar actions from a dropdown menu. + * + * @param {Window} win The window the imip message is opened in. + * @param {string} buttonId The id of the containing the menu. + * @param {string} actionId The id of the menu item to click. + */ +async function clickMenuAction(win, buttonId, actionId) { + let aboutMessage = win.document.getElementById("messageBrowser").contentWindow; + let actionButton = aboutMessage.document.getElementById(buttonId); + await TestUtils.waitForCondition(() => !actionButton.hidden, `"${buttonId}" shown`); + + let actionMenu = actionButton.querySelector("menupopup"); + let menuShown = BrowserTestUtils.waitForEvent(actionMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(actionButton.querySelector("dropmarker"), {}, aboutMessage); + await menuShown; + actionMenu.activateItem(aboutMessage.document.getElementById(actionId)); + await TestUtils.waitForCondition(() => actionButton.hidden, `action menu "#${buttonId}" hidden`); +} + +const unpromotedProps = ["location", "description", "sequence", "x-moz-received-dtstamp"]; + +/** + * An object where the keys are paths/selectors and the values are the values + * we expect to encounter. + * + * @typedef {object} Comparable + */ + +/** + * Compares the paths specified in the expected object against the provided + * actual object. + * + * @param {object} actual This is expected to be a calIEvent or calIAttendee but + * can also be an array of both etc. + * @param {Comparable} expected + */ +function compareProperties(actual, expected, prefix = "") { + Assert.equal(typeof actual, "object", `${prefix || "provided value"} is an object`); + for (let [key, value] of Object.entries(expected)) { + if (key.includes(".")) { + let keys = key.split("."); + let head = keys[0]; + let tail = keys.slice(1).join("."); + compareProperties(actual[head], { [tail]: value }, [prefix, head].filter(k => k).join(".")); + continue; + } + + let path = [prefix, key].filter(k => k).join("."); + let actualValue = unpromotedProps.includes(key) ? actual.getProperty(key) : actual[key]; + Assert.equal(actualValue, value, `property "${path}" is "${value}"`); + } +} + +/** + * Compares the text contents of the selectors specified on the inviatation panel + * to the expected value for each. + * + * @param {ShadowRoot} root The invitation panel's ShadowRoot instance. + * @param {Comparable} expected + */ +function compareShownPanelValues(root, expected) { + for (let [key, value] of Object.entries(expected)) { + value = Array.isArray(value) ? value.join("") : value; + Assert.equal( + root.querySelector(key).textContent.trim(), + value, + `property "${key}" is "${value}"` + ); + } +} + +/** + * Clicks on one of the invitation panel action buttons. + * + * @param {Window} panel + * @param {string} id + * @param {boolean} sendResponse + */ +async function clickPanelAction(panel, id, sendResponse = true) { + let promise = BrowserTestUtils.promiseAlertDialogOpen(sendResponse ? "accept" : "cancel"); + let button = panel.shadowRoot.getElementById(id); + EventUtils.synthesizeMouseAtCenter(button, {}, panel.ownerGlobal); + await promise; + await BrowserTestUtils.waitForEvent(panel.ownerGlobal, "onItipItemActionFinished"); +} + +/** + * Tests that an attempt to reply to the organizer of the event with the correct + * details occurred. + * + * @param {EmailTransport} transport + * @param {nsIdentity} identity + * @param {string} partStat + */ +async function doReplyTest(transport, identity, partStat) { + info("Verifying the attempt to send a response uses the correct data"); + Assert.equal(transport.sentItems.length, 1, "itip subsystem attempted to send a response"); + compareProperties(transport.sentItems[0], { + "recipients.0.id": "mailto:sender@example.com", + "itipItem.responseMethod": "REPLY", + "fromAttendee.id": "mailto:receiver@example.com", + "fromAttendee.participationStatus": partStat, + }); + + // The itipItem is used to generate the iTIP data in the message body. + info("Verifying the reply calItipItem attendee list"); + let replyItem = transport.sentItems[0].itipItem.getItemList()[0]; + let replyAttendees = replyItem.getAttendees(); + Assert.equal(replyAttendees.length, 1, "reply has one attendee"); + compareProperties(replyAttendees[0], { + id: "mailto:receiver@example.com", + participationStatus: partStat, + }); + + info("Verifying the call to the message subsystem"); + Assert.equal(transport.sentMsgs.length, 1, "transport sent 1 message"); + compareProperties(transport.sentMsgs[0], { + userIdentity: identity, + "composeFields.from": "receiver@example.com", + "composeFields.to": "Sender ", + }); + Assert.ok(transport.sentMsgs[0].messageFile.exists(), "message file was created"); +} + +/** + * @typedef {object} ImipBarActionTestConf + * + * @property {calICalendar} calendar The calendar used for the test. + * @property {calIItipTranport} transport The transport used for the test. + * @property {nsIIdentity} identity The identity expected to be used to + * send the reply. + * @property {boolean} isRecurring Indicates whether to treat the event as a + * recurring event or not. + * @property {string} partStat The participationStatus of the receiving user to + * expect. + * @property {boolean} noReply If true, do not expect an attempt to send a reply. + * @property {boolean} noSend If true, expect the reply attempt to stop after the + * user is prompted. + * @property {boolean} isMajor For update tests indicates if the changes expected + * are major or minor. + */ + +/** + * Test the properties of an event created from the imip-bar and optionally, the + * attempt to send a reply. + * + * @param {ImipBarActionTestConf} conf + * @param {calIEvent|calIEvent[]} item + */ +async function doImipBarActionTest(conf, event) { + let { calendar, transport, identity, partStat, isRecurring, noReply, noSend } = conf; + let events = [event]; + let startDates = ["20220316T110000Z"]; + let endDates = ["20220316T113000Z"]; + + if (isRecurring) { + startDates = [...startDates, "20220317T110000Z", "20220318T110000Z"]; + endDates = [...endDates, "20220317T113000Z", "20220318T113000Z"]; + events = event.parentItem.recurrenceInfo.getOccurrences( + cal.createDateTime("19700101"), + cal.createDateTime("30000101"), + Infinity + ); + Assert.equal(events.length, 3, "reccurring event has 3 occurrences"); + } + + info("Verifying relevant properties of each event occurrence"); + for (let [index, occurrence] of events.entries()) { + compareProperties(occurrence, { + id: "02e79b96", + title: isRecurring ? "Repeat Event" : "Single Event", + "calendar.name": calendar.name, + ...(isRecurring ? { "recurrenceId.icalString": startDates[index] } : {}), + "startDate.icalString": startDates[index], + "endDate.icalString": endDates[index], + description: "An event invitation.", + location: "Somewhere", + sequence: "0", + "x-moz-received-dtstamp": "20220316T191602Z", + "organizer.id": "mailto:sender@example.com", + status: "CONFIRMED", + }); + + // Alarms should be ignored. + Assert.equal( + occurrence.getAlarms().length, + 0, + `${isRecurring ? "occurrence" : "event"} has no reminders` + ); + + info("Verifying attendee list and participation status"); + let attendees = occurrence.getAttendees(); + compareProperties(attendees, { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.participationStatus": partStat, + "1.id": "mailto:receiver@example.com", + "2.id": "mailto:other@example.com", + "2.participationStatus": "NEEDS-ACTION", + }); + } + + if (noReply) { + Assert.equal( + transport.sentItems.length, + 0, + "itip subsystem did not attempt to send a response" + ); + } + if (noReply || noSend) { + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); + return; + } + await doReplyTest(transport, identity, partStat); +} + +/** + * Tests the recognition and application of a minor update to an existing event. + * An update is considered minor if the SEQUENCE property has not changed but + * the DTSTAMP has. + * + * @param {ImipBarActionTestConf} conf + */ +async function doMinorUpdateTest(conf) { + let { transport, calendar, partStat, isRecurring } = conf; + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + let prevEventIcs = event.icalString; + + transport.reset(); + + let updatePath = isRecurring ? "data/repeat-update-minor.eml" : "data/update-minor.eml"; + let win = await openImipMessage(new FileUtils.File(getTestFilePath(updatePath))); + let aboutMessage = win.document.getElementById("messageBrowser").contentWindow; + let updateButton = aboutMessage.document.getElementById("imipUpdateButton"); + Assert.ok(!updateButton.hidden, `#${updateButton.id} button shown`); + EventUtils.synthesizeMouseAtCenter(updateButton, {}, aboutMessage); + + await TestUtils.waitForCondition(async () => { + event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + return event.icalString != prevEventIcs; + }, "event updated"); + + await BrowserTestUtils.closeWindow(win); + + let events = [event]; + let startDates = ["20220316T110000Z"]; + let endDates = ["20220316T113000Z"]; + if (isRecurring) { + startDates = [...startDates, "20220317T110000Z", "20220318T110000Z"]; + endDates = [...endDates, "20220317T113000Z", "20220318T113000Z"]; + events = event.recurrenceInfo.getOccurrences( + cal.createDateTime("19700101"), + cal.createDateTime("30000101"), + Infinity + ); + Assert.equal(events.length, 3, "reccurring event has 3 occurrences"); + } + + info("Verifying relevant properties of each event occurrence"); + for (let [index, occurrence] of events.entries()) { + compareProperties(occurrence, { + id: "02e79b96", + title: "Updated Event", + "calendar.name": calendar.name, + ...(isRecurring ? { "recurrenceId.icalString": startDates[index] } : {}), + "startDate.icalString": startDates[index], + "endDate.icalString": endDates[index], + description: "Updated description.", + location: "Updated location", + sequence: "0", + "x-moz-received-dtstamp": "20220318T191602Z", + "organizer.id": "mailto:sender@example.com", + status: "CONFIRMED", + }); + + // Note: It seems we do not keep the order of the attendees list for updates. + let attendees = occurrence.getAttendees(); + compareProperties(attendees, { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.id": "mailto:other@example.com", + "1.participationStatus": "NEEDS-ACTION", + "2.participationStatus": partStat, + "2.id": "mailto:receiver@example.com", + }); + } + + Assert.equal(transport.sentItems.length, 0, "itip subsystem did not attempt to send a response"); + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); + await calendar.deleteItem(event); +} + +const actionIds = { + single: { + button: { + ACCEPTED: "imipAcceptButton", + TENTATIVE: "imipTentativeButton", + DECLINED: "imipDeclineButton", + }, + noReply: { + ACCEPTED: "imipAcceptButton_AcceptDontSend", + TENTATIVE: "imipTentativeButton_TentativeDontSend", + DECLINED: "imipDeclineButton_DeclineDontSend", + }, + }, + recurring: { + button: { + ACCEPTED: "imipAcceptRecurrencesButton", + TENTATIVE: "imipTentativeRecurrencesButton", + DECLINED: "imipDeclineRecurrencesButton", + }, + noReply: { + ACCEPTED: "imipAcceptRecurrencesButton_AcceptDontSend", + TENTATIVE: "imipTentativeRecurrencesButton_TentativeDontSend", + DECLINED: "imipDeclineRecurrencesButton_DeclineDontSend", + }, + }, +}; + +/** + * Tests the recognition and application of a major update to an existing event. + * An update is considered major if the SEQUENCE property has changed. For major + * updates, the imip-bar prompts the user to re-confirm their attendance. + * + * @param {ImipBarActionTestConf} conf + */ +async function doMajorUpdateTest(conf) { + let { transport, identity, calendar, partStat, isRecurring, noReply } = conf; + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + let prevEventIcs = event.icalString; + + transport.reset(); + + let updatePath = isRecurring ? "data/repeat-update-major.eml" : "data/update-major.eml"; + let win = await openImipMessage(new FileUtils.File(getTestFilePath(updatePath))); + let actions = isRecurring ? actionIds.recurring : actionIds.single; + if (noReply) { + let { button, noReply } = actions; + await clickMenuAction(win, button[partStat], noReply[partStat]); + } else { + await clickAction(win, actions.button[partStat]); + } + + await TestUtils.waitForCondition(async () => { + event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + return event.icalString != prevEventIcs; + }, "event updated"); + + await BrowserTestUtils.closeWindow(win); + + if (noReply) { + Assert.equal( + transport.sentItems.length, + 0, + "itip subsystem did not attempt to send a response" + ); + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); + } else { + await doReplyTest(transport, identity, partStat); + } + + let events = [event]; + let startDates = ["20220316T050000Z"]; + let endDates = ["20220316T053000Z"]; + if (isRecurring) { + startDates = [...startDates, "20220317T050000Z", "20220318T050000Z"]; + endDates = [...endDates, "20220317T053000Z", "20220318T053000Z"]; + events = event.recurrenceInfo.getOccurrences( + cal.createDateTime("19700101"), + cal.createDateTime("30000101"), + Infinity + ); + Assert.equal(events.length, 3, "reccurring event has 3 occurrences"); + } + + for (let [index, occurrence] of events.entries()) { + compareProperties(occurrence, { + id: "02e79b96", + title: isRecurring ? "Repeat Event" : "Single Event", + "calendar.name": calendar.name, + ...(isRecurring ? { "recurrenceId.icalString": startDates[index] } : {}), + "startDate.icalString": startDates[index], + "endDate.icalString": endDates[index], + description: "An event invitation.", + location: "Somewhere", + sequence: "2", + "x-moz-received-dtstamp": "20220316T191602Z", + "organizer.id": "mailto:sender@example.com", + status: "CONFIRMED", + }); + + let attendees = occurrence.getAttendees(); + compareProperties(attendees, { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.id": "mailto:other@example.com", + "1.participationStatus": "NEEDS-ACTION", + "2.participationStatus": partStat, + "2.id": "mailto:receiver@example.com", + }); + } + await calendar.deleteItem(event); +} + +/** + * Tests the recognition and application of a minor update exception to an + * existing recurring event. + * + * @param {ImipBarActionTestConf} conf + */ +async function doMinorExceptionTest(conf) { + let { transport, calendar, partStat } = conf; + let recurrenceId = cal.createDateTime("20220317T110000Z"); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + let originalProps = { + id: "02e79b96", + "recurrenceId.icalString": "20220317T110000Z", + title: event.title, + "calendar.name": calendar.name, + "startDate.icalString": event.startDate.icalString, + "endDate.icalString": event.endDate.icalString, + description: event.getProperty("DESCRIPTION"), + location: event.getProperty("LOCATION"), + sequence: "0", + "x-moz-received-dtstamp": event.getProperty("x-moz-received-dtstamp"), + "organizer.id": "mailto:sender@example.com", + status: "CONFIRMED", + }; + + Assert.ok( + !event.recurrenceInfo.getExceptionFor(recurrenceId), + `no exception exists for ${recurrenceId}` + ); + + transport.reset(); + + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-minor.eml"))); + let aboutMessage = win.document.getElementById("messageBrowser").contentWindow; + let updateButton = aboutMessage.document.getElementById("imipUpdateButton"); + Assert.ok(!updateButton.hidden, `#${updateButton.id} button shown`); + EventUtils.synthesizeMouseAtCenter(updateButton, {}, aboutMessage); + + let exception; + await TestUtils.waitForCondition(async () => { + event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + exception = event.recurrenceInfo.getExceptionFor(recurrenceId); + return exception; + }, "event exception applied"); + + await BrowserTestUtils.closeWindow(win); + + Assert.equal(transport.sentItems.length, 0, "itip subsystem did not attempt to send a response"); + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); + + info("Verifying relevant properties of the exception"); + compareProperties(exception, { + id: "02e79b96", + "recurrenceId.icalString": "20220317T110000Z", + title: "Exception title", + "calendar.name": calendar.name, + "startDate.icalString": "20220317T110000Z", + "endDate.icalString": "20220317T113000Z", + description: "Exception description", + location: "Exception location", + sequence: "0", + "x-moz-received-dtstamp": "20220318T191602Z", + "organizer.id": "mailto:sender@example.com", + status: "CONFIRMED", + }); + + compareProperties(exception.getAttendees(), { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.id": "mailto:other@example.com", + "1.participationStatus": "NEEDS-ACTION", + "2.id": "mailto:receiver@example.com", + "2.participationStatus": partStat, + }); + + let occurrences = event.recurrenceInfo.getOccurrences( + cal.createDateTime("19700101"), + cal.createDateTime("30000101"), + Infinity + ); + Assert.equal(occurrences.length, 3, "reccurring event still has 3 occurrences"); + + info("Verifying relevant properties of the other occurrences"); + + let startDates = ["20220316T110000Z", "20220317T110000Z", "20220318T110000Z"]; + let endDates = ["20220316T113000Z", "20220317T113000Z", "20220318T113000Z"]; + for (let [index, occurrence] of occurrences.entries()) { + if (occurrence.startDate.compare(recurrenceId) == 0) { + continue; + } + compareProperties(occurrence, { + ...originalProps, + "recurrenceId.icalString": startDates[index], + "startDate.icalString": startDates[index], + "endDate.icalString": endDates[index], + }); + + let attendees = occurrence.getAttendees(); + compareProperties(attendees, { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.id": "mailto:receiver@example.com", + "1.participationStatus": partStat, + "2.id": "mailto:other@example.com", + "2.participationStatus": "NEEDS-ACTION", + }); + } + + await calendar.deleteItem(event); +} + +/** + * Tests the recognition and application of a major update exception to an + * existing recurring event. + * + * @param {ImipBarActionTestConf} conf + */ +async function doMajorExceptionTest(conf) { + let { transport, identity, calendar, partStat, noReply } = conf; + let recurrenceId = cal.createDateTime("20220317T110000Z"); + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + let originalProps = { + id: "02e79b96", + "recurrenceId.icalString": "20220317T110000Z", + title: event.title, + "calendar.name": calendar.name, + "startDate.icalString": event.startDate.icalString, + "endDate.icalString": event.endDate.icalString, + description: event.getProperty("DESCRIPTION"), + location: event.getProperty("LOCATION"), + sequence: "0", + "x-moz-received-dtstamp": event.getProperty("x-moz-received-dtstamp"), + "organizer.id": "mailto:sender@example.com", + status: "CONFIRMED", + }; + let originalPartStat = event + .getAttendees() + .find(att => att.id == "mailto:receiver@example.com").participationStatus; + + Assert.ok( + !event.recurrenceInfo.getExceptionFor(recurrenceId), + `no exception exists for ${recurrenceId}` + ); + + transport.reset(); + + let win = await openImipMessage(new FileUtils.File(getTestFilePath("data/exception-major.eml"))); + if (noReply) { + let { button, noReply } = actionIds.single; + await clickMenuAction(win, button[partStat], noReply[partStat]); + } else { + await clickAction(win, actionIds.single.button[partStat]); + } + + let exception; + await TestUtils.waitForCondition(async () => { + event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + exception = event.recurrenceInfo.getExceptionFor(recurrenceId); + return exception; + }, "event exception applied"); + + await BrowserTestUtils.closeWindow(win); + + if (noReply) { + Assert.equal( + transport.sentItems.length, + 0, + "itip subsystem did not attempt to send a response" + ); + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); + } else { + await doReplyTest(transport, identity, partStat); + } + + info("Verifying relevant properties of the exception"); + + compareProperties(exception, { + ...originalProps, + "startDate.icalString": "20220317T050000Z", + "endDate.icalString": "20220317T053000Z", + sequence: "2", + }); + + compareProperties(exception.getAttendees(), { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.id": "mailto:other@example.com", + "1.participationStatus": "NEEDS-ACTION", + "2.id": "mailto:receiver@example.com", + "2.participationStatus": partStat, + }); + + let occurrences = event.recurrenceInfo.getOccurrences( + cal.createDateTime("19700101"), + cal.createDateTime("30000101"), + Infinity + ); + Assert.equal(occurrences.length, 3, "reccurring event still has 3 occurrences"); + + info("Verifying relevant properties of the other occurrences"); + + let startDates = ["20220316T110000Z", "20220317T110000Z", "20220318T110000Z"]; + let endDates = ["20220316T113000Z", "20220317T113000Z", "20220318T113000Z"]; + for (let [index, occurrence] of occurrences.entries()) { + if (occurrence.startDate.icalString == "20220317T050000Z") { + continue; + } + compareProperties(occurrence, { + ...originalProps, + "recurrenceId.icalString": startDates[index], + "startDate.icalString": startDates[index], + "endDate.icalString": endDates[index], + }); + + let attendees = occurrence.getAttendees(); + compareProperties(attendees, { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.id": "mailto:receiver@example.com", + "1.participationStatus": originalPartStat, + "2.id": "mailto:other@example.com", + "2.participationStatus": "NEEDS-ACTION", + }); + } + + await calendar.deleteItem(event); +} + +/** + * Test the properties of an event created from a minor or major exception where + * we have not added the original event and optionally, the attempt to send a + * reply. + * + * @param {ImipBarActionTestConf} conf + */ +async function doExceptionOnlyTest(conf) { + let { calendar, transport, identity, partStat, noReply, isMajor } = conf; + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 5, 1)).item; + + // Exceptions are still created as recurring events. + Assert.ok(event != event.parentItem, "event created is a recurring event"); + let occurrences = event.parentItem.recurrenceInfo.getOccurrences( + cal.createDateTime("10000101"), + cal.createDateTime("30000101"), + Infinity + ); + Assert.equal(occurrences.length, 1, "parent item only has one occurrence"); + Assert.ok(occurrences[0] == event, "occurrence is the event exception"); + + info("Verifying relevant properties of the event"); + compareProperties(event, { + id: "02e79b96", + title: isMajor ? event.title : "Exception title", + "calendar.name": calendar.name, + "recurrenceId.icalString": "20220317T110000Z", + "startDate.icalString": isMajor ? "20220317T050000Z" : "20220317T110000Z", + "endDate.icalString": isMajor ? "20220317T053000Z" : "20220317T113000Z", + description: isMajor ? event.getProperty("DESCRIPTION") : "Exception description", + location: isMajor ? event.getProperty("LOCATION") : "Exception location", + sequence: isMajor ? "2" : "0", + "x-moz-received-dtstamp": isMajor + ? event.getProperty("x-moz-received-dtstamp") + : "20220318T191602Z", + "organizer.id": "mailto:sender@example.com", + status: "CONFIRMED", + }); + + // Alarms should be ignored. + Assert.equal(event.getAlarms().length, 0, "event has no reminders"); + + info("Verifying attendee list and participation status"); + let attendees = event.getAttendees(); + compareProperties(attendees, { + "0.id": "mailto:sender@example.com", + "0.participationStatus": "ACCEPTED", + "1.participationStatus": partStat, + "1.id": "mailto:receiver@example.com", + "2.id": "mailto:other@example.com", + "2.participationStatus": "NEEDS-ACTION", + }); + + if (noReply) { + Assert.equal( + transport.sentItems.length, + 0, + "itip subsystem did not attempt to send a response" + ); + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); + } else { + await doReplyTest(transport, identity, partStat); + } + await calendar.deleteItem(event.parentItem); +} + +/** + * Tests the recognition and application of a cancellation to an existing event. + * + * @param {ImipBarActionTestConf} conf + */ +async function doCancelTest({ transport, calendar, isRecurring, event, recurrenceId }) { + transport.reset(); + + let eventId = event.id; + if (isRecurring) { + // wait for the other occurrences to appear. + await CalendarTestUtils.monthView.waitForItemAt(window, 3, 5, 1); + await CalendarTestUtils.monthView.waitForItemAt(window, 3, 6, 1); + } + + let cancellationPath = isRecurring + ? "data/cancel-repeat-event.eml" + : "data/cancel-single-event.eml"; + + let cancelMsgFile = new FileUtils.File(getTestFilePath(cancellationPath)); + if (recurrenceId) { + let srcTxt = await IOUtils.readUTF8(cancelMsgFile.path); + srcTxt = srcTxt.replaceAll(/RRULE:.+/g, `RECURRENCE-ID:${recurrenceId}`); + srcTxt = srcTxt.replaceAll(/SEQUENCE:.+/g, "SEQUENCE:3"); + cancelMsgFile = FileTestUtils.getTempFile("cancel-occurrence.eml"); + await IOUtils.writeUTF8(cancelMsgFile.path, srcTxt); + } + + let win = await openImipMessage(cancelMsgFile); + let aboutMessage = win.document.getElementById("messageBrowser").contentWindow; + let deleteButton = aboutMessage.document.getElementById("imipDeleteButton"); + Assert.ok(!deleteButton.hidden, `#${deleteButton.id} button shown`); + EventUtils.synthesizeMouseAtCenter(deleteButton, {}, aboutMessage); + + if (isRecurring && recurrenceId) { + // Expects a single occurrence to be cancelled. + + let occurrences; + await TestUtils.waitForCondition(async () => { + let { parentItem } = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item; + occurrences = parentItem.recurrenceInfo.getOccurrences( + cal.createDateTime("19700101"), + cal.createDateTime("30000101"), + Infinity + ); + return occurrences.length == 2; + }, "occurrence was deleted"); + + Assert.ok( + occurrences.every(occ => occ.recurrenceId && occ.recurrenceId != recurrenceId), + `occurrence "${recurrenceId}" removed` + ); + Assert.ok(!!(await calendar.getItem(eventId)), "event was not deleted"); + } else { + await CalendarTestUtils.monthView.waitForNoItemAt(window, 3, 4, 1); + + if (isRecurring) { + await CalendarTestUtils.monthView.waitForNoItemAt(window, 3, 5, 1); + await CalendarTestUtils.monthView.waitForNoItemAt(window, 3, 6, 1); + } + + await TestUtils.waitForCondition(async () => { + let result = await calendar.getItem(eventId); + return !result; + }, "event was deleted"); + } + + await BrowserTestUtils.closeWindow(win); + Assert.equal(transport.sentItems.length, 0, "itip subsystem did not attempt to send a response"); + Assert.equal(transport.sentMsgs.length, 0, "no call was made into the mail subsystem"); +} + +/** + * Tests processing of cancellations to exceptions to recurring events. + * + * @param {ImipBarActionTestConf} conf + */ +async function doCancelExceptionTest(conf) { + let { partStat, recurrenceId, calendar } = conf; + let invite = new FileUtils.File(getTestFilePath("data/repeat-event.eml")); + let win = await openImipMessage(invite); + await clickAction(win, actionIds.recurring.button[partStat]); + + let event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + await BrowserTestUtils.closeWindow(win); + + let update = new FileUtils.File(getTestFilePath("data/exception-major.eml")); + let updateWin = await openImipMessage(update); + await clickAction(updateWin, actionIds.single.button[partStat]); + + let exception; + await TestUtils.waitForCondition(async () => { + event = (await CalendarTestUtils.monthView.waitForItemAt(window, 3, 4, 1)).item.parentItem; + exception = event.recurrenceInfo.getExceptionFor(cal.createDateTime(recurrenceId)); + return !!exception; + }, "exception applied"); + + await BrowserTestUtils.closeWindow(updateWin); + await doCancelTest({ ...conf, event }); + await calendar.deleteItem(event); +} -- cgit v1.2.3