diff options
Diffstat (limited to '')
7 files changed, 698 insertions, 0 deletions
diff --git a/comm/calendar/test/browser/providers/browser.ini b/comm/calendar/test/browser/providers/browser.ini new file mode 100644 index 0000000000..84d6133696 --- /dev/null +++ b/comm/calendar/test/browser/providers/browser.ini @@ -0,0 +1,21 @@ +[default] +head = head.js +prefs = + calendar.item.promptDelete=false + calendar.debug.log=true + calendar.debug.log.verbose=true + 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 + +[browser_caldavCalendar_cached.js] +[browser_caldavCalendar_uncached.js] +[browser_icsCalendar_cached.js] +[browser_icsCalendar_uncached.js] +[browser_storageCalendar.js] diff --git a/comm/calendar/test/browser/providers/browser_caldavCalendar_cached.js b/comm/calendar/test/browser/providers/browser_caldavCalendar_cached.js new file mode 100644 index 0000000000..5b725e4d54 --- /dev/null +++ b/comm/calendar/test/browser/providers/browser_caldavCalendar_cached.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { CalDAVServer } = ChromeUtils.import("resource://testing-common/calendar/CalDAVServer.jsm"); + +CalDAVServer.open("bob", "bob"); +if (!Services.logins.findLogins(CalDAVServer.origin, null, "test").length) { + // Save a username and password to the login manager. + let loginInfo = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo); + loginInfo.init(CalDAVServer.origin, null, "test", "bob", "bob", "", ""); + Services.logins.addLogin(loginInfo); +} + +let calendar; +add_setup(async function () { + calendarObserver._onLoadPromise = PromiseUtils.defer(); + calendar = createCalendar("caldav", CalDAVServer.url, true); + await calendarObserver._onLoadPromise.promise; + info("calendar set-up complete"); + + registerCleanupFunction(async () => { + // This test has issues cleaning up, and it breaks all the subsequent tests. + await new Promise(r => setTimeout(r, 1000)); // eslint-disable-line mozilla/no-arbitrary-setTimeout + await CalDAVServer.close(); + Services.logins.removeAllLogins(); + removeCalendar(calendar); + }); +}); + +async function promiseIdle() { + await TestUtils.waitForCondition(() => !calendar.wrappedJSObject.mPendingSync); + await fetch(`${CalDAVServer.origin}/ping`); +} + +add_task(async function testAlarms() { + calendarObserver._batchRequired = true; + await runTestAlarms(calendar); + + // Be sure the calendar has finished deleting the event. + await promiseIdle(); +}); + +add_task(async function testSyncChanges() { + await syncChangesTest.setUp(); + + await CalDAVServer.putItemInternal( + "ad0850e5-8020-4599-86a4-86c90af4e2cd.ics", + syncChangesTest.part1Item + ); + await syncChangesTest.runPart1(); + + await CalDAVServer.putItemInternal( + "ad0850e5-8020-4599-86a4-86c90af4e2cd.ics", + syncChangesTest.part2Item + ); + await syncChangesTest.runPart2(); + + CalDAVServer.deleteItemInternal("ad0850e5-8020-4599-86a4-86c90af4e2cd.ics"); + await syncChangesTest.runPart3(); + + // Be sure the calendar has finished all requests. + await promiseIdle(); +}); diff --git a/comm/calendar/test/browser/providers/browser_caldavCalendar_uncached.js b/comm/calendar/test/browser/providers/browser_caldavCalendar_uncached.js new file mode 100644 index 0000000000..7489ae4e09 --- /dev/null +++ b/comm/calendar/test/browser/providers/browser_caldavCalendar_uncached.js @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { CalDAVServer } = ChromeUtils.import("resource://testing-common/calendar/CalDAVServer.jsm"); + +CalDAVServer.open("bob", "bob"); +if (!Services.logins.findLogins(CalDAVServer.origin, null, "test").length) { + // Save a username and password to the login manager. + let loginInfo = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo); + loginInfo.init(CalDAVServer.origin, null, "test", "bob", "bob", "", ""); + Services.logins.addLogin(loginInfo); +} + +let calendar; +add_setup(async function () { + calendarObserver._onLoadPromise = PromiseUtils.defer(); + calendar = createCalendar("caldav", CalDAVServer.url, false); + await calendarObserver._onLoadPromise.promise; + info("calendar set-up complete"); + + registerCleanupFunction(async () => { + await CalDAVServer.close(); + Services.logins.removeAllLogins(); + removeCalendar(calendar); + }); +}); + +async function promiseIdle() { + await fetch(`${CalDAVServer.origin}/ping`); +} + +add_task(async function testAlarms() { + calendarObserver._batchRequired = true; + await runTestAlarms(calendar); + + // Be sure the calendar has finished deleting the event. + await promiseIdle(); +}); + +add_task(async function testSyncChanges() { + await syncChangesTest.setUp(); + + await CalDAVServer.putItemInternal( + "ad0850e5-8020-4599-86a4-86c90af4e2cd.ics", + syncChangesTest.part1Item + ); + await syncChangesTest.runPart1(); + + await CalDAVServer.putItemInternal( + "ad0850e5-8020-4599-86a4-86c90af4e2cd.ics", + syncChangesTest.part2Item + ); + await syncChangesTest.runPart2(); + + CalDAVServer.deleteItemInternal("ad0850e5-8020-4599-86a4-86c90af4e2cd.ics"); + await syncChangesTest.runPart3(); + + // Be sure the calendar has finished all requests. + await promiseIdle(); +}); diff --git a/comm/calendar/test/browser/providers/browser_icsCalendar_cached.js b/comm/calendar/test/browser/providers/browser_icsCalendar_cached.js new file mode 100644 index 0000000000..ba788be5b9 --- /dev/null +++ b/comm/calendar/test/browser/providers/browser_icsCalendar_cached.js @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { ICSServer } = ChromeUtils.import("resource://testing-common/calendar/ICSServer.jsm"); + +ICSServer.open("bob", "bob"); +if (!Services.logins.findLogins(ICSServer.origin, null, "test").length) { + // Save a username and password to the login manager. + let loginInfo = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo); + loginInfo.init(ICSServer.origin, null, "test", "bob", "bob", "", ""); + Services.logins.addLogin(loginInfo); +} + +let calendar; +add_setup(async function () { + // TODO: item notifications from a cached ICS calendar occur outside of batches. + // This isn't fatal but it shouldn't happen. Side-effects include alarms firing + // twice - once from onAddItem then again at onLoad. + // + // Remove the next line when this is fixed. + calendarObserver._batchRequired = false; + + calendarObserver._onLoadPromise = PromiseUtils.defer(); + calendar = createCalendar("ics", ICSServer.url, true); + await calendarObserver._onLoadPromise.promise; + info("calendar set-up complete"); + + registerCleanupFunction(async () => { + await ICSServer.close(); + Services.logins.removeAllLogins(); + removeCalendar(calendar); + }); +}); + +async function promiseIdle() { + await TestUtils.waitForCondition( + () => + calendar.wrappedJSObject.mUncachedCalendar.wrappedJSObject._queue.length == 0 && + calendar.wrappedJSObject.mUncachedCalendar.wrappedJSObject._isLocked === false + ); + await fetch(`${ICSServer.origin}/ping`); +} + +add_task(async function testAlarms() { + // Remove the next line when fixed. + calendarObserver._batchRequired = false; + await runTestAlarms(calendar); + + // Be sure the calendar has finished deleting the event. + await promiseIdle(); +}).skip(); // Broken. + +add_task(async function testSyncChanges() { + await syncChangesTest.setUp(); + + await ICSServer.putICSInternal(syncChangesTest.part1Item); + await syncChangesTest.runPart1(); + + await ICSServer.putICSInternal(syncChangesTest.part2Item); + await syncChangesTest.runPart2(); + + await ICSServer.putICSInternal( + CalendarTestUtils.dedent` + BEGIN:VCALENDAR + END:VCALENDAR + ` + ); + await syncChangesTest.runPart3(); + + // Be sure the calendar has finished deleting the event. + await promiseIdle(); +}); diff --git a/comm/calendar/test/browser/providers/browser_icsCalendar_uncached.js b/comm/calendar/test/browser/providers/browser_icsCalendar_uncached.js new file mode 100644 index 0000000000..ef25408dce --- /dev/null +++ b/comm/calendar/test/browser/providers/browser_icsCalendar_uncached.js @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ + +var { ICSServer } = ChromeUtils.import("resource://testing-common/calendar/ICSServer.jsm"); + +ICSServer.open("bob", "bob"); +if (!Services.logins.findLogins(ICSServer.origin, null, "test").length) { + // Save a username and password to the login manager. + let loginInfo = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo); + loginInfo.init(ICSServer.origin, null, "test", "bob", "bob", "", ""); + Services.logins.addLogin(loginInfo); +} + +let calendar; +add_setup(async function () { + calendarObserver._onLoadPromise = PromiseUtils.defer(); + calendar = createCalendar("ics", ICSServer.url, false); + await calendarObserver._onLoadPromise.promise; + info("calendar set-up complete"); + + registerCleanupFunction(async () => { + await ICSServer.close(); + Services.logins.removeAllLogins(); + removeCalendar(calendar); + }); +}); + +async function promiseIdle() { + await TestUtils.waitForCondition( + () => + calendar.wrappedJSObject._queue.length == 0 && calendar.wrappedJSObject._isLocked === false + ); + await fetch(`${ICSServer.origin}/ping`); +} + +add_task(async function testAlarms() { + calendarObserver._batchRequired = true; + await runTestAlarms(calendar); + + // Be sure the calendar has finished deleting the event. + await promiseIdle(); +}); + +add_task(async function testSyncChanges() { + await syncChangesTest.setUp(); + + await ICSServer.putICSInternal(syncChangesTest.part1Item); + await syncChangesTest.runPart1(); + + await ICSServer.putICSInternal(syncChangesTest.part2Item); + await syncChangesTest.runPart2(); + + await ICSServer.putICSInternal( + CalendarTestUtils.dedent` + BEGIN:VCALENDAR + END:VCALENDAR + ` + ); + await syncChangesTest.runPart3(); + + // Be sure the calendar has finished all requests. + await promiseIdle(); +}); diff --git a/comm/calendar/test/browser/providers/browser_storageCalendar.js b/comm/calendar/test/browser/providers/browser_storageCalendar.js new file mode 100644 index 0000000000..1a9eb6a30c --- /dev/null +++ b/comm/calendar/test/browser/providers/browser_storageCalendar.js @@ -0,0 +1,13 @@ +/* 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/. */ + +let calendar = createCalendar("storage", "moz-storage-calendar://"); +registerCleanupFunction(() => { + removeCalendar(calendar); +}); + +add_task(function testAlarms() { + calendarObserver._batchRequired = false; + return runTestAlarms(calendar); +}); diff --git a/comm/calendar/test/browser/providers/head.js b/comm/calendar/test/browser/providers/head.js new file mode 100644 index 0000000000..bf58302131 --- /dev/null +++ b/comm/calendar/test/browser/providers/head.js @@ -0,0 +1,402 @@ +/* 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/. */ + +SimpleTest.requestCompleteLog(); + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { CalendarTestUtils } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarTestUtils.jsm" +); +var { handleDeleteOccurrencePrompt } = ChromeUtils.import( + "resource://testing-common/calendar/CalendarUtils.jsm" +); + +var { saveAndCloseItemDialog, setData } = ChromeUtils.import( + "resource://testing-common/calendar/ItemEditingHelpers.jsm" +); + +let calendarObserver = { + QueryInterface: ChromeUtils.generateQI(["calIObserver"]), + + /* calIObserver */ + + _batchCount: 0, + _batchRequired: true, + onStartBatch(calendar) { + info(`onStartBatch ${calendar?.id} ${++this._batchCount}`); + Assert.equal( + calendar, + this._expectedCalendar, + "onStartBatch should occur on the expected calendar" + ); + }, + onEndBatch(calendar) { + info(`onEndBatch ${calendar?.id} ${this._batchCount--}`); + Assert.equal( + calendar, + this._expectedCalendar, + "onEndBatch should occur on the expected calendar" + ); + }, + onLoad(calendar) { + info(`onLoad ${calendar.id}`); + Assert.equal(calendar, this._expectedCalendar, "onLoad should occur on the expected calendar"); + if (this._onLoadPromise) { + this._onLoadPromise.resolve(); + } + }, + onAddItem(item) { + info(`onAddItem ${item.calendar.id} ${item.id}`); + if (this._batchRequired) { + Assert.equal(this._batchCount, 1, "onAddItem must occur in a batch"); + } + }, + onModifyItem(newItem, oldItem) { + info(`onModifyItem ${newItem.calendar.id} ${newItem.id}`); + if (this._batchRequired) { + Assert.equal(this._batchCount, 1, "onModifyItem must occur in a batch"); + } + }, + onDeleteItem(deletedItem) { + info(`onDeleteItem ${deletedItem.calendar.id} ${deletedItem.id}`); + }, + onError(calendar, errNo, message) {}, + onPropertyChanged(calendar, name, value, oldValue) {}, + onPropertyDeleting(calendar, name) {}, +}; + +/** + * Create and register a calendar. + * + * @param {string} type - The calendar provider to use. + * @param {string} url - URL of the server. + * @param {boolean} useCache - Should this calendar have offline storage? + * @returns {calICalendar} + */ +function createCalendar(type, url, useCache) { + let calendar = cal.manager.createCalendar(type, Services.io.newURI(url)); + calendar.name = type + (useCache ? " with cache" : " without cache"); + calendar.id = cal.getUUID(); + calendar.setProperty("cache.enabled", useCache); + calendar.setProperty("calendar-main-default", true); + + cal.manager.registerCalendar(calendar); + calendar = cal.manager.getCalendarById(calendar.id); + calendarObserver._expectedCalendar = calendar; + calendar.addObserver(calendarObserver); + + info(`Created calendar ${calendar.id}`); + return calendar; +} + +/** + * Unregister a calendar. + * + * @param {calICalendar} calendar + */ +function removeCalendar(calendar) { + calendar.removeObserver(calendarObserver); + cal.manager.removeCalendar(calendar); +} + +let alarmService = Cc["@mozilla.org/calendar/alarm-service;1"].getService(Ci.calIAlarmService); + +let alarmObserver = { + QueryInterface: ChromeUtils.generateQI(["calIAlarmServiceObserver"]), + + /* calIAlarmServiceObserver */ + + _alarmCount: 0, + onAlarm(item, alarm) { + info("onAlarm"); + this._alarmCount++; + }, + onRemoveAlarmsByItem(item) {}, + onRemoveAlarmsByCalendar(calendar) {}, + onAlarmsLoaded(calendar) {}, +}; +alarmService.addObserver(alarmObserver); +registerCleanupFunction(async () => { + alarmService.removeObserver(alarmObserver); +}); + +/** + * Tests the creation, firing, dismissal, modification and deletion of an event with an alarm. + * Also checks that the number of events in the unifinder is correct at each stage. + * + * Passing this test requires the active calendar to fire notifications in the correct sequence. + */ +async function runTestAlarms() { + let today = cal.dtz.now(); + let start = today.clone(); + start.day++; + start.hour = start.minute = start.second = 0; + let end = start.clone(); + end.hour++; + let repeatUntil = start.clone(); + repeatUntil.day += 15; + + await CalendarTestUtils.setCalendarView(window, "multiweek"); + await CalendarTestUtils.goToToday(window); + Assert.equal(window.unifinderTreeView.rowCount, 0, "unifinder event count"); + + alarmObserver._alarmCount = 0; + + let alarmDialogPromise = BrowserTestUtils.promiseAlertDialog( + undefined, + "chrome://calendar/content/calendar-alarm-dialog.xhtml", + { + async callback(alarmWindow) { + info("Alarm dialog opened"); + let alarmDocument = alarmWindow.document; + + let list = alarmDocument.getElementById("alarm-richlist"); + let items = list.querySelectorAll(`richlistitem[is="calendar-alarm-widget-richlistitem"]`); + await TestUtils.waitForCondition(() => items.length); + Assert.equal(items.length, 1); + + await new Promise(resolve => alarmWindow.setTimeout(resolve, 500)); + + let dismissButton = alarmDocument.querySelector("#alarm-dismiss-all-button"); + EventUtils.synthesizeMouseAtCenter(dismissButton, {}, alarmWindow); + }, + } + ); + let { dialogWindow, iframeWindow } = await CalendarTestUtils.editNewEvent(window); + await setData(dialogWindow, iframeWindow, { + title: "test event", + startdate: start, + starttime: start, + enddate: end, + endtime: end, + reminder: "2days", + repeat: "weekly", + }); + + await saveAndCloseItemDialog(dialogWindow); + await alarmDialogPromise; + info("Alarm dialog closed"); + + await new Promise(r => setTimeout(r, 2000)); + Assert.equal(window.unifinderTreeView.rowCount, 1, "there should be one event in the unifinder"); + + Assert.equal( + [...Services.wm.getEnumerator("Calendar:AlarmWindow")].length, + 0, + "alarm dialog did not reappear" + ); + Assert.equal(alarmObserver._alarmCount, 1, "only one alarm"); + alarmObserver._alarmCount = 0; + + let eventBox = await CalendarTestUtils.multiweekView.waitForItemAt( + window, + start.weekday == 0 ? 2 : 1, // Sunday's event is next week. + start.weekday + 1, + 1 + ); + Assert.ok(!!eventBox.item.parentItem.alarmLastAck); + + ({ dialogWindow, iframeWindow } = await CalendarTestUtils.editItemOccurrences(window, eventBox)); + await setData(dialogWindow, iframeWindow, { + title: "modified test event", + repeat: "weekly", + repeatuntil: repeatUntil, + }); + + await saveAndCloseItemDialog(dialogWindow); + + Assert.equal(window.unifinderTreeView.rowCount, 1, "there should be one event in the unifinder"); + + Services.focus.focusedWindow = window; + + await new Promise(resolve => setTimeout(resolve, 2000)); + Assert.equal( + [...Services.wm.getEnumerator("Calendar:AlarmWindow")].length, + 0, + "alarm dialog should not reappear" + ); + Assert.equal(alarmObserver._alarmCount, 0, "there should not be any remaining alarms"); + alarmObserver._alarmCount = 0; + + eventBox = await CalendarTestUtils.multiweekView.waitForItemAt( + window, + start.weekday == 0 ? 2 : 1, // Sunday's event is next week. + start.weekday + 1, + 1 + ); + Assert.ok(!!eventBox.item.parentItem.alarmLastAck); + + EventUtils.synthesizeMouseAtCenter(eventBox, {}, window); + eventBox.focus(); + window.calendarController.onSelectionChanged({ detail: window.currentView().getSelectedItems() }); + await handleDeleteOccurrencePrompt(window, window.currentView(), true); + + await CalendarTestUtils.multiweekView.waitForNoItemAt( + window, + start.weekday == 0 ? 2 : 1, // Sunday's event is next week. + start.weekday + 1, + 1 + ); + Assert.equal(window.unifinderTreeView.rowCount, 0, "there should be no events in the unifinder"); +} + +const syncItem1Name = "holy cow, a new item!"; +const syncItem2Name = "a changed item"; + +let syncChangesTest = { + async setUp() { + await CalendarTestUtils.openCalendarTab(window); + + if (document.getElementById("today-pane-panel").collapsed) { + EventUtils.synthesizeMouseAtCenter( + document.getElementById("calendar-status-todaypane-button"), + {} + ); + } + + if (document.getElementById("agenda-panel").collapsed) { + EventUtils.synthesizeMouseAtCenter(document.getElementById("today-pane-cycler-next"), {}); + } + }, + + get part1Item() { + let today = cal.dtz.now(); + let start = today.clone(); + start.day += 9 - start.weekday; + start.hour = 13; + start.minute = start.second = 0; + let end = start.clone(); + end.hour++; + + return CalendarTestUtils.dedent` + BEGIN:VCALENDAR + BEGIN:VEVENT + UID:ad0850e5-8020-4599-86a4-86c90af4e2cd + SUMMARY:${syncItem1Name} + DTSTART:${start.icalString} + DTEND:${end.icalString} + END:VEVENT + END:VCALENDAR + `; + }, + + async runPart1() { + await CalendarTestUtils.setCalendarView(window, "multiweek"); + await CalendarTestUtils.goToToday(window); + + // Sanity check that we have not already synchronized and that there is no + // existing item. + Assert.ok( + !CalendarTestUtils.multiweekView.getItemAt(window, 2, 3, 1), + "there should be no existing item in the calendar" + ); + + // Synchronize. + EventUtils.synthesizeMouseAtCenter(document.getElementById("refreshCalendar"), {}); + + // Verify that the item we added appears in the calendar view. + let item = await CalendarTestUtils.multiweekView.waitForItemAt(window, 2, 3, 1); + Assert.equal(item.item.title, syncItem1Name, "view should include newly-added item"); + + // Verify that the today pane updates and shows the item we added. + await TestUtils.waitForCondition(() => window.TodayPane.agenda.rowCount == 1); + Assert.equal( + getTodayPaneItemTitle(0), + syncItem1Name, + "today pane should include newly-added item" + ); + Assert.ok( + !window.TodayPane.agenda.rows[0].nextElementSibling, + "there should be no additional items in the today pane" + ); + }, + + get part2Item() { + let today = cal.dtz.now(); + let start = today.clone(); + start.day += 10 - start.weekday; + start.hour = 9; + start.minute = start.second = 0; + let end = start.clone(); + end.hour++; + + return CalendarTestUtils.dedent` + BEGIN:VCALENDAR + BEGIN:VEVENT + UID:ad0850e5-8020-4599-86a4-86c90af4e2cd + SUMMARY:${syncItem2Name} + DTSTART:${start.icalString} + DTEND:${end.icalString} + END:VEVENT + END:VCALENDAR + `; + }, + + async runPart2() { + // Sanity check that we have not already synchronized and that there is no + // existing item. + Assert.ok( + !CalendarTestUtils.multiweekView.getItemAt(window, 2, 4, 1), + "there should be no existing item on the specified day" + ); + + // Synchronize. + EventUtils.synthesizeMouseAtCenter(document.getElementById("refreshCalendar"), {}); + + // Verify that the item has updated in the calendar view. + await CalendarTestUtils.multiweekView.waitForNoItemAt(window, 2, 3, 1); + let item = await CalendarTestUtils.multiweekView.waitForItemAt(window, 2, 4, 1); + Assert.equal(item.item.title, syncItem2Name, "view should show updated item"); + + // Verify that the today pane updates and shows the updated item. + await TestUtils.waitForCondition( + () => window.TodayPane.agenda.rowCount == 1 && getTodayPaneItemTitle(0) != syncItem1Name + ); + Assert.equal(getTodayPaneItemTitle(0), syncItem2Name, "today pane should show updated item"); + Assert.ok( + !window.TodayPane.agenda.rows[0].nextElementSibling, + "there should be no additional items in the today pane" + ); + }, + + async runPart3() { + // Synchronize via the calendar context menu. + await calendarListContextMenu( + document.querySelector("#calendar-list > li:nth-child(2)"), + "list-calendar-context-reload" + ); + + // Verify that the item is removed from the calendar view. + await CalendarTestUtils.multiweekView.waitForNoItemAt(window, 2, 3, 1); + await CalendarTestUtils.multiweekView.waitForNoItemAt(window, 2, 4, 1); + + // Verify that the item is removed from the today pane. + await TestUtils.waitForCondition(() => window.TodayPane.agenda.rowCount == 0); + }, +}; + +function getTodayPaneItemTitle(idx) { + const row = window.TodayPane.agenda.rows[idx]; + return row.querySelector(".agenda-listitem-title").textContent; +} + +async function calendarListContextMenu(target, menuItem) { + await new Promise(r => setTimeout(r)); + window.focus(); + await TestUtils.waitForCondition( + () => Services.focus.focusedWindow == window, + "waiting for window to be focused" + ); + + let contextMenu = document.getElementById("list-calendars-context-menu"); + let shownPromise = BrowserTestUtils.waitForEvent(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(target, { type: "contextmenu" }); + await shownPromise; + + if (menuItem) { + let hiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + contextMenu.activateItem(document.getElementById(menuItem)); + await hiddenPromise; + } +} |