summaryrefslogtreecommitdiffstats
path: root/comm/calendar/test/browser/browser_todayPane.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/calendar/test/browser/browser_todayPane.js')
-rw-r--r--comm/calendar/test/browser/browser_todayPane.js820
1 files changed, 820 insertions, 0 deletions
diff --git a/comm/calendar/test/browser/browser_todayPane.js b/comm/calendar/test/browser/browser_todayPane.js
new file mode 100644
index 0000000000..8ad9141815
--- /dev/null
+++ b/comm/calendar/test/browser/browser_todayPane.js
@@ -0,0 +1,820 @@
+/* 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/. */
+
+/* globals TodayPane */
+
+var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+var { formatDate, formatTime } = ChromeUtils.import(
+ "resource://testing-common/calendar/ItemEditingHelpers.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ CalDateTime: "resource:///modules/CalDateTime.jsm",
+ CalEvent: "resource:///modules/CalEvent.jsm",
+ CalRecurrenceInfo: "resource:///modules/CalRecurrenceInfo.jsm",
+});
+
+let calendar = CalendarTestUtils.createCalendar();
+Services.prefs.setIntPref("calendar.agenda.days", 7);
+registerCleanupFunction(() => {
+ CalendarTestUtils.removeCalendar(calendar);
+ Services.prefs.clearUserPref("calendar.agenda.days");
+});
+
+let today = cal.dtz.now();
+let startHour = today.hour;
+today.hour = today.minute = today.second = 0;
+
+let todayPanePanel = document.getElementById("today-pane-panel");
+let todayPaneStatusButton = document.getElementById("calendar-status-todaypane-button");
+
+// Go to mail tab.
+selectFolderTab();
+
+// Verify today pane open.
+if (todayPanePanel.hasAttribute("collapsed")) {
+ EventUtils.synthesizeMouseAtCenter(todayPaneStatusButton, {});
+}
+Assert.ok(!todayPanePanel.hasAttribute("collapsed"), "Today Pane is open");
+
+// Verify today pane's date.
+Assert.equal(document.getElementById("datevalue-label").value, today.day, "Today Pane shows today");
+
+async function addEvent(title, relativeStart, relativeEnd, isAllDay) {
+ let event = new CalEvent();
+ event.id = cal.getUUID();
+ event.title = title;
+ event.startDate = today.clone();
+ event.startDate.addDuration(cal.createDuration(relativeStart));
+ event.startDate.isDate = isAllDay;
+ event.endDate = today.clone();
+ event.endDate.addDuration(cal.createDuration(relativeEnd));
+ event.endDate.isDate = isAllDay;
+ return calendar.addItem(event);
+}
+
+function checkEvent(row, { dateHeader, time, title, relative, overlap, classes = [] }) {
+ let dateHeaderElement = row.querySelector(".agenda-date-header");
+ if (dateHeader) {
+ Assert.ok(BrowserTestUtils.is_visible(dateHeaderElement), "date header is visible");
+ if (dateHeader instanceof CalDateTime || dateHeader instanceof Ci.calIDateTime) {
+ dateHeader = cal.dtz.formatter.formatDateLongWithoutYear(dateHeader);
+ }
+ Assert.equal(dateHeaderElement.textContent, dateHeader, "date header has correct value");
+ } else {
+ Assert.ok(BrowserTestUtils.is_hidden(dateHeaderElement), "date header is hidden");
+ }
+
+ let calendarElement = row.querySelector(".agenda-listitem-calendar");
+ let timeElement = row.querySelector(".agenda-listitem-time");
+ if (time) {
+ Assert.ok(BrowserTestUtils.is_visible(calendarElement), "calendar is visible");
+ Assert.ok(BrowserTestUtils.is_visible(timeElement), "time is visible");
+ if (time instanceof CalDateTime || time instanceof Ci.calIDateTime) {
+ time = cal.dtz.formatter.formatTime(time);
+ }
+ Assert.equal(timeElement.textContent, time, "time has correct value");
+ } else if (time === "") {
+ Assert.ok(BrowserTestUtils.is_visible(calendarElement), "calendar is visible");
+ Assert.ok(BrowserTestUtils.is_hidden(timeElement), "time is hidden");
+ } else {
+ Assert.ok(BrowserTestUtils.is_hidden(calendarElement), "calendar is hidden");
+ Assert.ok(BrowserTestUtils.is_hidden(timeElement), "time is hidden");
+ }
+
+ let titleElement = row.querySelector(".agenda-listitem-title");
+ Assert.ok(BrowserTestUtils.is_visible(titleElement), "title is visible");
+ Assert.equal(titleElement.textContent, title, "title has correct value");
+
+ let relativeElement = row.querySelector(".agenda-listitem-relative");
+ if (Array.isArray(relative)) {
+ Assert.ok(BrowserTestUtils.is_visible(relativeElement), "relative time is visible");
+ Assert.report(
+ !relative.includes(relativeElement.textContent),
+ relative,
+ relativeElement.textContent,
+ "relative time is correct",
+ "includes"
+ );
+ } else if (relative !== undefined) {
+ Assert.ok(BrowserTestUtils.is_hidden(relativeElement), "relative time is hidden");
+ }
+
+ let overlapElement = row.querySelector(".agenda-listitem-overlap");
+ if (overlap) {
+ Assert.ok(BrowserTestUtils.is_visible(overlapElement), "overlap is visible");
+ Assert.equal(
+ overlapElement.src,
+ `chrome://messenger/skin/icons/new/event-${overlap}.svg`,
+ "overlap has correct image"
+ );
+ Assert.equal(
+ overlapElement.dataset.l10nId,
+ `calendar-editable-item-multiday-event-icon-${overlap}`,
+ "overlap has correct alt text"
+ );
+ } else {
+ Assert.ok(BrowserTestUtils.is_hidden(overlapElement), "overlap is hidden");
+ }
+
+ for (let className of classes) {
+ Assert.ok(row.classList.contains(className), `row has ${className} class`);
+ }
+}
+
+function checkEvents(...expectedEvents) {
+ Assert.equal(TodayPane.agenda.rowCount, expectedEvents.length, "expected number of rows shown");
+ for (let i = 0; i < expectedEvents.length; i++) {
+ Assert.ok(TodayPane.agenda.rows[i].getAttribute("is"), "agenda-listitem");
+ checkEvent(TodayPane.agenda.rows[i], expectedEvents[i]);
+ }
+}
+
+add_task(async function testBasicAllDay() {
+ let todaysEvent = await addEvent("Today's Event", "P0D", "P1D", true);
+ checkEvents({ dateHeader: "Today", title: "Today's Event" });
+
+ let tomorrowsEvent = await addEvent("Tomorrow's Event", "P1D", "P2D", true);
+ checkEvents(
+ { dateHeader: "Today", title: "Today's Event" },
+ { dateHeader: "Tomorrow", title: "Tomorrow's Event" }
+ );
+
+ let events = [];
+ for (let i = 2; i < 7; i++) {
+ events.push(await addEvent(`Event ${i + 1}`, `P${i}D`, `P${i + 1}D`, true));
+ checkEvents(
+ { dateHeader: "Today", title: "Today's Event" },
+ { dateHeader: "Tomorrow", title: "Tomorrow's Event" },
+ ...events.map(e => {
+ return { dateHeader: e.startDate, title: e.title };
+ })
+ );
+ }
+
+ await calendar.deleteItem(todaysEvent);
+ checkEvents(
+ { dateHeader: "Tomorrow", title: "Tomorrow's Event" },
+ ...events.map(e => {
+ return { dateHeader: e.startDate, title: e.title };
+ })
+ );
+ await calendar.deleteItem(tomorrowsEvent);
+ checkEvents(
+ ...events.map(e => {
+ return { dateHeader: e.startDate, title: e.title };
+ })
+ );
+
+ while (events.length) {
+ await calendar.deleteItem(events.shift());
+ checkEvents(
+ ...events.map(e => {
+ return { dateHeader: e.startDate, title: e.title };
+ })
+ );
+ }
+});
+
+add_task(async function testBasic() {
+ let time = today.clone();
+ time.hour = 23;
+
+ let todaysEvent = await addEvent("Today's Event", "P0DT23H", "P1D");
+ checkEvents({ dateHeader: "Today", time, title: "Today's Event" });
+
+ let tomorrowsEvent = await addEvent("Tomorrow's Event", "P1DT23H", "P2D");
+ checkEvents(
+ { dateHeader: "Today", time, title: "Today's Event" },
+ { dateHeader: "Tomorrow", time, title: "Tomorrow's Event" }
+ );
+
+ let events = [];
+ for (let i = 2; i < 7; i++) {
+ events.push(await addEvent(`Event ${i + 1}`, `P${i}DT23H`, `P${i + 1}D`));
+ checkEvents(
+ { dateHeader: "Today", time, title: "Today's Event" },
+ { dateHeader: "Tomorrow", time, title: "Tomorrow's Event" },
+ ...events.map(e => {
+ return { dateHeader: e.startDate, time, title: e.title };
+ })
+ );
+ }
+
+ await calendar.deleteItem(todaysEvent);
+ checkEvents(
+ { dateHeader: "Tomorrow", time, title: "Tomorrow's Event" },
+ ...events.map(e => {
+ return { dateHeader: e.startDate, time, title: e.title };
+ })
+ );
+ await calendar.deleteItem(tomorrowsEvent);
+ checkEvents(
+ ...events.map(e => {
+ return { dateHeader: e.startDate, time, title: e.title };
+ })
+ );
+
+ while (events.length) {
+ await calendar.deleteItem(events.shift());
+ checkEvents(
+ ...events.map(e => {
+ return { dateHeader: e.startDate, time, title: e.title };
+ })
+ );
+ }
+});
+
+/**
+ * Adds and removes events in a different order from which they occur.
+ * This checks that the events are inserted in the right place, and that the
+ * date header is shown/hidden appropriately.
+ */
+add_task(async function testSortOrder() {
+ let afternoonEvent = await addEvent("Afternoon Event", "P1DT13H", "P1DT17H");
+ checkEvents({
+ dateHeader: "Tomorrow",
+ time: afternoonEvent.startDate,
+ title: "Afternoon Event",
+ });
+
+ let morningEvent = await addEvent("Morning Event", "P1DT8H", "P1DT12H");
+ checkEvents(
+ { dateHeader: "Tomorrow", time: morningEvent.startDate, title: "Morning Event" },
+ { time: afternoonEvent.startDate, title: "Afternoon Event" }
+ );
+
+ let allDayEvent = await addEvent("All Day Event", "P1D", "P2D", true);
+ checkEvents(
+ { dateHeader: "Tomorrow", title: "All Day Event" },
+ { time: morningEvent.startDate, title: "Morning Event" },
+ { time: afternoonEvent.startDate, title: "Afternoon Event" }
+ );
+
+ let eveningEvent = await addEvent("Evening Event", "P1DT18H", "P1DT22H");
+ checkEvents(
+ { dateHeader: "Tomorrow", title: "All Day Event" },
+ { time: morningEvent.startDate, title: "Morning Event" },
+ { time: afternoonEvent.startDate, title: "Afternoon Event" },
+ { time: eveningEvent.startDate, title: "Evening Event" }
+ );
+
+ await calendar.deleteItem(afternoonEvent);
+ checkEvents(
+ { dateHeader: "Tomorrow", title: "All Day Event" },
+ { time: morningEvent.startDate, title: "Morning Event" },
+ { time: eveningEvent.startDate, title: "Evening Event" }
+ );
+
+ await calendar.deleteItem(morningEvent);
+ checkEvents(
+ { dateHeader: "Tomorrow", title: "All Day Event" },
+ { time: eveningEvent.startDate, title: "Evening Event" }
+ );
+
+ await calendar.deleteItem(allDayEvent);
+ checkEvents({
+ dateHeader: "Tomorrow",
+ time: eveningEvent.startDate,
+ title: "Evening Event",
+ });
+
+ await calendar.deleteItem(eveningEvent);
+ checkEvents();
+});
+
+/**
+ * Check events that begin and end on different days inside the date range.
+ * All-day events are still sorted ahead of non-all-day events.
+ */
+add_task(async function testOverlapInside() {
+ let allDayEvent = await addEvent("All Day Event", "P0D", "P2D", true);
+ checkEvents(
+ { dateHeader: "Today", title: "All Day Event", overlap: "start" },
+ { dateHeader: "Tomorrow", title: "All Day Event", overlap: "end" }
+ );
+
+ let timedEvent = await addEvent("Timed Event", "P1H", "P1D23H");
+ checkEvents(
+ { dateHeader: "Today", title: "All Day Event", overlap: "start" },
+ { time: timedEvent.startDate, title: "Timed Event", overlap: "start" },
+ { dateHeader: "Tomorrow", title: "All Day Event", overlap: "end" },
+ { time: timedEvent.endDate, title: "Timed Event", overlap: "end" }
+ );
+
+ await calendar.deleteItem(allDayEvent);
+ await calendar.deleteItem(timedEvent);
+});
+
+/**
+ * Check events that begin and end on different days and that end at midnight.
+ * The list item for the end of the event should be the last one on the day
+ * before the end midnight, and its time label should display "24:00".
+ */
+add_task(async function testOverlapEndAtMidnight() {
+ // Start with an event that begins outside the displayed dates.
+
+ let timedEvent = await addEvent("Timed Event", "-P1D", "P1D");
+ // Ends an hour before `timedEvent` to prove the ordering is correct.
+ let duringEvent = await addEvent("During Event", "P22H", "P23H");
+ // Starts at the same time as `timedEvent` ends to prove the ordering is correct.
+ let nextEvent = await addEvent("Next Event", "P1D", "P2D", true);
+
+ checkEvents(
+ { dateHeader: "Today", time: duringEvent.startDate, title: "During Event" },
+ {
+ // Should show "24:00" as the time and end today.
+ time: cal.dtz.formatter.formatTime(timedEvent.endDate, true),
+ title: "Timed Event",
+ overlap: "end",
+ },
+ { dateHeader: "Tomorrow", title: "Next Event" }
+ );
+
+ // Move the event fully into the displayed range.
+
+ let timedClone = timedEvent.clone();
+ timedClone.startDate.day += 2;
+ timedClone.endDate.day += 2;
+ await calendar.modifyItem(timedClone, timedEvent);
+
+ let duringClone = duringEvent.clone();
+ duringClone.startDate.day += 2;
+ duringClone.endDate.day += 2;
+ await calendar.modifyItem(duringClone, duringEvent);
+
+ let nextClone = nextEvent.clone();
+ nextClone.startDate.day += 2;
+ nextClone.endDate.day += 2;
+ await calendar.modifyItem(nextClone, nextEvent);
+
+ let realEndDate = today.clone();
+ realEndDate.day += 2;
+ checkEvents(
+ {
+ dateHeader: "Tomorrow",
+ time: timedClone.startDate,
+ title: "Timed Event",
+ overlap: "start",
+ },
+ { dateHeader: realEndDate, time: duringClone.startDate, title: "During Event" },
+ {
+ // Should show "24:00" as the time and end on the day after tomorrow.
+ time: cal.dtz.formatter.formatTime(timedClone.endDate, true),
+ title: "Timed Event",
+ overlap: "end",
+ },
+ { dateHeader: nextClone.startDate, title: "Next Event" }
+ );
+
+ await calendar.deleteItem(timedClone);
+ await calendar.deleteItem(duringClone);
+ await calendar.deleteItem(nextClone);
+});
+
+/**
+ * Check events that begin and/or end outside the date range. Events that have
+ * already started are listed as "Today", but still sorted by start time.
+ * All-day events are still sorted ahead of non-all-day events.
+ */
+add_task(async function testOverlapOutside() {
+ let before = await addEvent("Starts Before", "-P1D", "P1D", true);
+ checkEvents({ dateHeader: "Today", title: "Starts Before", overlap: "end" });
+
+ let after = await addEvent("Ends After", "P0D", "P9D", true);
+ checkEvents(
+ { dateHeader: "Today", title: "Starts Before", overlap: "end" },
+ { title: "Ends After", overlap: "start" }
+ );
+
+ let both = await addEvent("Beyond Start and End", "-P2D", "P9D", true);
+ checkEvents(
+ { dateHeader: "Today", title: "Beyond Start and End", overlap: "continue" },
+ { title: "Starts Before", overlap: "end" },
+ { title: "Ends After", overlap: "start" }
+ );
+
+ // Change `before` to begin earlier than `both`. They should swap places.
+
+ let startClone = before.clone();
+ startClone.startDate.day -= 2;
+ await calendar.modifyItem(startClone, before);
+ checkEvents(
+ { dateHeader: "Today", title: "Starts Before", overlap: "end" },
+ { title: "Beyond Start and End", overlap: "continue" },
+ { title: "Ends After", overlap: "start" }
+ );
+
+ let beforeWithTime = await addEvent("Starts Before with time", "-PT5H", "PT15H");
+ checkEvents(
+ { dateHeader: "Today", title: "Starts Before", overlap: "end" },
+ { title: "Beyond Start and End", overlap: "continue" },
+ { title: "Ends After", overlap: "start" },
+ // This is the end of the event so the end time is used.
+ { time: beforeWithTime.endDate, title: "Starts Before with time", overlap: "end" }
+ );
+
+ let afterWithTime = await addEvent("Ends After with time", "PT6H", "P8DT12H");
+ checkEvents(
+ { dateHeader: "Today", title: "Starts Before", overlap: "end" },
+ { title: "Beyond Start and End", overlap: "continue" },
+ { title: "Ends After", overlap: "start" },
+ { time: afterWithTime.startDate, title: "Ends After with time", overlap: "start" },
+ // This is the end of the event so the end time is used.
+ { time: beforeWithTime.endDate, title: "Starts Before with time", overlap: "end" }
+ );
+
+ let bothWithTime = await addEvent("Beyond Start and End with time", "-P2DT10H", "P9DT1H");
+ checkEvents(
+ { dateHeader: "Today", title: "Starts Before", overlap: "end" },
+ { title: "Beyond Start and End", overlap: "continue" },
+ { title: "Ends After", overlap: "start" },
+ { time: "", title: "Beyond Start and End with time", overlap: "continue" },
+ { time: afterWithTime.startDate, title: "Ends After with time", overlap: "start" },
+ // This is the end of the event so the end time is used.
+ { time: beforeWithTime.endDate, title: "Starts Before with time", overlap: "end" }
+ );
+
+ await calendar.deleteItem(before);
+ await calendar.deleteItem(after);
+ await calendar.deleteItem(both);
+ await calendar.deleteItem(beforeWithTime);
+ await calendar.deleteItem(afterWithTime);
+ await calendar.deleteItem(bothWithTime);
+});
+
+/**
+ * Checks that events that happened earlier today are marked as in the past,
+ * and events happening now are marked as such.
+ *
+ * This test may fail if run within a minute either side of midnight.
+ *
+ * It would be nice to test that as time passes events are changed
+ * appropriately, but that means waiting around for minutes and probably won't
+ * be very reliable, so we don't do that.
+ */
+add_task(async function testActive() {
+ let now = cal.dtz.now();
+
+ let pastEvent = await addEvent("Past Event", "PT0M", "PT1M");
+ let presentEvent = await addEvent("Present Event", `PT${now.hour}H`, `PT${now.hour + 1}H`);
+ let futureEvent = await addEvent("Future Event", "PT23H59M", "PT24H");
+ checkEvents(
+ { dateHeader: "Today", time: pastEvent.startDate, title: "Past Event" },
+ { time: presentEvent.startDate, title: "Present Event" },
+ { time: futureEvent.startDate, title: "Future Event" }
+ );
+
+ let [pastRow, presentRow, futureRow] = TodayPane.agenda.rows;
+ Assert.ok(pastRow.classList.contains("agenda-listitem-past"), "past event is marked past");
+ Assert.ok(!pastRow.classList.contains("agenda-listitem-now"), "past event is not marked now");
+ Assert.ok(
+ !presentRow.classList.contains("agenda-listitem-past"),
+ "present event is not marked past"
+ );
+ Assert.ok(presentRow.classList.contains("agenda-listitem-now"), "present event is marked now");
+ Assert.ok(
+ !futureRow.classList.contains("agenda-listitem-past"),
+ "future event is not marked past"
+ );
+ Assert.ok(!futureRow.classList.contains("agenda-listitem-now"), "future event is not marked now");
+
+ await calendar.deleteItem(pastEvent);
+ await calendar.deleteItem(presentEvent);
+ await calendar.deleteItem(futureEvent);
+});
+
+/**
+ * Checks events in different time zones are displayed correctly.
+ */
+add_task(async function testOtherTimeZones() {
+ // Johannesburg is UTC+2.
+ let johannesburg = cal.timezoneService.getTimezone("Africa/Johannesburg");
+ // Panama is UTC-5.
+ let panama = cal.timezoneService.getTimezone("America/Panama");
+
+ // All-day events are displayed on the day of the event, the time zone is ignored.
+
+ let allDayEvent = new CalEvent();
+ allDayEvent.id = cal.getUUID();
+ allDayEvent.title = "All-day event in Johannesburg";
+ allDayEvent.startDate = cal.createDateTime();
+ allDayEvent.startDate.resetTo(today.year, today.month, today.day + 1, 0, 0, 0, johannesburg);
+ allDayEvent.startDate.isDate = true;
+ allDayEvent.endDate = cal.createDateTime();
+ allDayEvent.endDate.resetTo(today.year, today.month, today.day + 2, 0, 0, 0, johannesburg);
+ allDayEvent.endDate.isDate = true;
+ allDayEvent = await calendar.addItem(allDayEvent);
+
+ checkEvents({
+ dateHeader: "Tomorrow",
+ title: "All-day event in Johannesburg",
+ });
+
+ await calendar.deleteItem(allDayEvent);
+
+ // The event time must be displayed in the local time zone, and the event must be sorted correctly.
+
+ let beforeEvent = await addEvent("Before", "P1DT5H", "P1DT6H");
+ let afterEvent = await addEvent("After", "P1DT7H", "P1DT8H");
+
+ let timedEvent = new CalEvent();
+ timedEvent.id = cal.getUUID();
+ timedEvent.title = "Morning in Johannesburg";
+ timedEvent.startDate = cal.createDateTime();
+ timedEvent.startDate.resetTo(today.year, today.month, today.day + 1, 8, 0, 0, johannesburg);
+ timedEvent.endDate = cal.createDateTime();
+ timedEvent.endDate.resetTo(today.year, today.month, today.day + 1, 12, 0, 0, johannesburg);
+ timedEvent = await calendar.addItem(timedEvent);
+
+ checkEvents(
+ {
+ dateHeader: "Tomorrow",
+ time: beforeEvent.startDate,
+ title: "Before",
+ },
+ {
+ time: cal.dtz.formatter.formatTime(cal.createDateTime("20000101T060000Z")), // The date used here is irrelevant.
+ title: "Morning in Johannesburg",
+ },
+ {
+ time: afterEvent.startDate,
+ title: "After",
+ }
+ );
+ Assert.stringContains(
+ TodayPane.agenda.rows[1].querySelector(".agenda-listitem-time").getAttribute("datetime"),
+ "T08:00:00+02:00"
+ );
+
+ await calendar.deleteItem(beforeEvent);
+ await calendar.deleteItem(afterEvent);
+ await calendar.deleteItem(timedEvent);
+
+ // Events that cross midnight in the local time zone (but not in the event time zone)
+ // must have a start row and an end row.
+
+ let overnightEvent = new CalEvent();
+ overnightEvent.id = cal.getUUID();
+ overnightEvent.title = "Evening in Panama";
+ overnightEvent.startDate = cal.createDateTime();
+ overnightEvent.startDate.resetTo(today.year, today.month, today.day, 17, 0, 0, panama);
+ overnightEvent.endDate = cal.createDateTime();
+ overnightEvent.endDate.resetTo(today.year, today.month, today.day, 23, 0, 0, panama);
+ overnightEvent = await calendar.addItem(overnightEvent);
+
+ checkEvents(
+ {
+ dateHeader: "Today",
+ time: cal.dtz.formatter.formatTime(cal.createDateTime("20000101T220000Z")), // The date used here is irrelevant.
+ title: "Evening in Panama",
+ overlap: "start",
+ },
+ {
+ dateHeader: "Tomorrow",
+ time: cal.dtz.formatter.formatTime(cal.createDateTime("20000101T040000Z")), // The date used here is irrelevant.
+ title: "Evening in Panama",
+ overlap: "end",
+ }
+ );
+ Assert.stringContains(
+ TodayPane.agenda.rows[0].querySelector(".agenda-listitem-time").getAttribute("datetime"),
+ "T17:00:00-05:00"
+ );
+ Assert.stringContains(
+ TodayPane.agenda.rows[1].querySelector(".agenda-listitem-time").getAttribute("datetime"),
+ "T23:00:00-05:00"
+ );
+
+ await calendar.deleteItem(overnightEvent);
+});
+
+/**
+ * Checks events in different time zones are displayed correctly.
+ */
+add_task(async function testRelativeTime() {
+ let formatter = new Intl.RelativeTimeFormat(undefined, { style: "short" });
+ let now = cal.dtz.now();
+ now.second = 0;
+ info(`The time is now ${now}`);
+
+ let testData = [
+ {
+ name: "two hours ago",
+ start: "-PT1H55M",
+ expected: {
+ classes: ["agenda-listitem-past"],
+ },
+ minHour: 2,
+ },
+ {
+ name: "one hour ago",
+ start: "-PT1H5M",
+ expected: {
+ classes: ["agenda-listitem-past"],
+ },
+ minHour: 2,
+ },
+ {
+ name: "23 minutes ago",
+ start: "-PT23M",
+ expected: {
+ classes: ["agenda-listitem-past"],
+ },
+ minHour: 1,
+ },
+ {
+ name: "now",
+ start: "-PT5M",
+ expected: {
+ relative: ["now"],
+ classes: ["agenda-listitem-now"],
+ },
+ minHour: 1,
+ maxHour: 22,
+ },
+ {
+ name: "19 minutes ahead",
+ start: "PT19M",
+ expected: {
+ relative: [formatter.format(19, "minute"), formatter.format(18, "minute")],
+ },
+ maxHour: 22,
+ },
+ {
+ name: "one hour ahead",
+ start: "PT1H25M",
+ expected: {
+ relative: [formatter.format(85, "minute"), formatter.format(84, "minute")],
+ },
+ maxHour: 21,
+ },
+ {
+ name: "one and half hours ahead",
+ start: "PT1H35M",
+ expected: {
+ relative: [formatter.format(2, "hour")],
+ },
+ maxHour: 21,
+ },
+ {
+ name: "two hours ahead",
+ start: "PT1H49M",
+ expected: {
+ relative: [formatter.format(2, "hour")],
+ },
+ maxHour: 21,
+ },
+ ];
+
+ let events = [];
+ let expectedEvents = [];
+ for (let { name, start, expected, minHour, maxHour } of testData) {
+ if (minHour && now.hour < minHour) {
+ info(`Skipping ${name} because it's too early.`);
+ continue;
+ }
+ if (maxHour && now.hour > maxHour) {
+ info(`Skipping ${name} because it's too late.`);
+ continue;
+ }
+
+ let event = new CalEvent();
+ event.id = cal.getUUID();
+ event.title = name;
+ event.startDate = now.clone();
+ event.startDate.addDuration(cal.createDuration(start));
+ event.endDate = event.startDate.clone();
+ event.endDate.addDuration(cal.createDuration("PT10M"));
+ events.push(await calendar.addItem(event));
+
+ expectedEvents.push({ ...expected, title: name, time: event.startDate });
+ }
+
+ expectedEvents[0].dateHeader = "Today";
+ checkEvents(...expectedEvents);
+
+ for (let event of events) {
+ await calendar.deleteItem(event);
+ }
+});
+
+/**
+ * Tests the today pane opens events in the summary dialog for both
+ * non-recurring and recurring events.
+ */
+add_task(async function testOpenEvent() {
+ let noRepeatEvent = new CalEvent();
+ noRepeatEvent.id = "no repeat event";
+ noRepeatEvent.title = "No Repeat Event";
+ noRepeatEvent.startDate = today.clone();
+ noRepeatEvent.startDate.hour = startHour;
+ noRepeatEvent.endDate = noRepeatEvent.startDate.clone();
+ noRepeatEvent.endDate.hour++;
+
+ let repeatEvent = new CalEvent();
+ repeatEvent.id = "repeated event";
+ repeatEvent.title = "Repeated Event";
+ repeatEvent.startDate = today.clone();
+ repeatEvent.startDate.hour = startHour;
+ repeatEvent.endDate = noRepeatEvent.startDate.clone();
+ repeatEvent.endDate.hour++;
+ repeatEvent.recurrenceInfo = new CalRecurrenceInfo(repeatEvent);
+ repeatEvent.recurrenceInfo.appendRecurrenceItem(
+ cal.createRecurrenceRule("RRULE:FREQ=DAILY;COUNT=5")
+ );
+
+ for (let event of [noRepeatEvent, repeatEvent]) {
+ let addedEvent = await calendar.addItem(event);
+
+ if (event == noRepeatEvent) {
+ Assert.equal(TodayPane.agenda.rowCount, 1);
+ } else {
+ Assert.equal(TodayPane.agenda.rowCount, 5);
+ }
+ Assert.equal(
+ TodayPane.agenda.rows[0].querySelector(".agenda-listitem-title").textContent,
+ event.title,
+ "event title is correct"
+ );
+
+ let dialogWindowPromise = CalendarTestUtils.waitForEventDialog();
+ EventUtils.synthesizeMouseAtCenter(TodayPane.agenda.rows[0], { clickCount: 2 });
+
+ let dialogWindow = await dialogWindowPromise;
+ let docUri = dialogWindow.document.documentURI;
+ Assert.ok(
+ docUri === "chrome://calendar/content/calendar-summary-dialog.xhtml",
+ "event summary dialog shown"
+ );
+
+ await BrowserTestUtils.closeWindow(dialogWindow);
+ await calendar.deleteItem(addedEvent);
+ }
+});
+
+/**
+ * Tests that the "New Event" button begins creating an event on the date
+ * selected in the Today Pane.
+ */
+add_task(async function testNewEvent() {
+ async function checkEventDialogDate() {
+ let dialogWindowPromise = CalendarTestUtils.waitForEventDialog("edit");
+ EventUtils.synthesizeMouseAtCenter(newEventButton, {}, window);
+ await dialogWindowPromise.then(async function (dialogWindow) {
+ let iframe = dialogWindow.document.querySelector("#calendar-item-panel-iframe");
+ let iframeDocument = iframe.contentDocument;
+
+ let startDate = iframeDocument.getElementById("event-starttime");
+ Assert.equal(
+ startDate._datepicker._inputField.value,
+ formatDate(expectedDate),
+ "date should match the expected date"
+ );
+ Assert.equal(
+ startDate._timepicker._inputField.value,
+ formatTime(expectedDate),
+ "time should be the next hour after now"
+ );
+
+ await BrowserTestUtils.closeWindow(dialogWindow);
+ });
+ }
+
+ let newEventButton = document.getElementById("todaypane-new-event-button");
+
+ // Check today with the "day" view.
+
+ TodayPane.displayMiniSection("miniday");
+ EventUtils.synthesizeMouseAtCenter(document.getElementById("today-button"), {}, window);
+
+ let expectedDate = cal.dtz.now();
+ expectedDate.hour++;
+ expectedDate.minute = 0;
+
+ await checkEventDialogDate();
+
+ // Check tomorrow with the "day" view.
+
+ EventUtils.synthesizeMouseAtCenter(document.getElementById("next-day-button"), {}, window);
+ expectedDate.day++;
+
+ await checkEventDialogDate();
+
+ // Check today with the "month" view;
+
+ TodayPane.displayMiniSection("minimonth");
+ let minimonth = document.getElementById("today-minimonth");
+ minimonth.value = new Date();
+ expectedDate.day--;
+
+ await checkEventDialogDate();
+
+ // Check a date in the past with the "month" view;
+
+ minimonth.value = new Date(Date.UTC(2018, 8, 1));
+ expectedDate.resetTo(2018, 8, 1, expectedDate.hour, 0, 0, cal.dtz.UTC);
+
+ await checkEventDialogDate();
+}).__skipMe = new Date().getUTCHours() == 23;