summaryrefslogtreecommitdiffstats
path: root/comm/calendar/test/unit/test_filter_tree_view.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/calendar/test/unit/test_filter_tree_view.js')
-rw-r--r--comm/calendar/test/unit/test_filter_tree_view.js451
1 files changed, 451 insertions, 0 deletions
diff --git a/comm/calendar/test/unit/test_filter_tree_view.js b/comm/calendar/test/unit/test_filter_tree_view.js
new file mode 100644
index 0000000000..97313849c4
--- /dev/null
+++ b/comm/calendar/test/unit/test_filter_tree_view.js
@@ -0,0 +1,451 @@
+/* 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/. */
+
+const { CalendarTestUtils } = ChromeUtils.import(
+ "resource://testing-common/calendar/CalendarTestUtils.jsm"
+);
+const { TestUtils } = ChromeUtils.importESModule("resource://testing-common/TestUtils.sys.mjs");
+
+const { CalEvent } = ChromeUtils.import("resource:///modules/CalEvent.jsm");
+const { CalRecurrenceInfo } = ChromeUtils.import("resource:///modules/CalRecurrenceInfo.jsm");
+const { CalRecurrenceRule } = ChromeUtils.import("resource:///modules/CalRecurrenceRule.jsm");
+
+const { TreeSelection } = ChromeUtils.importESModule(
+ "chrome://messenger/content/tree-selection.mjs"
+);
+
+Services.scriptloader.loadSubScript("chrome://messenger/content/jsTreeView.js");
+Services.scriptloader.loadSubScript("chrome://calendar/content/widgets/calendar-filter.js");
+/* globals CalendarFilteredTreeView */
+Services.scriptloader.loadSubScript(
+ "chrome://calendar/content/widgets/calendar-filter-tree-view.js"
+);
+
+const testItems = {};
+
+add_setup(async function () {
+ await new Promise(resolve => do_calendar_startup(resolve));
+
+ // Create events useful for testing.
+ for (const [title, startDate, endDate] of [
+ ["one", "20221126T010000", "20221126T013000"],
+ ["two", "20221126T020000", "20221126T073000"],
+ ["three", "20221126T030000", "20221126T033000"],
+ ["four", "20221126T040000", "20221126T043000"],
+ ["five", "20221126T050000", "20221126T053000"],
+ ["six", "20221126T060000", "20221126T063000"],
+ ]) {
+ const item = new CalEvent();
+ item.id = cal.getUUID();
+ item.title = title;
+ item.startDate = cal.createDateTime(startDate);
+ item.endDate = cal.createDateTime(endDate);
+ testItems[title] = item;
+ }
+
+ const recurring = new CalEvent();
+ recurring.id = cal.getUUID();
+ recurring.title = "recurring event";
+ recurring.startDate = cal.createDateTime("20221124T053000");
+ recurring.endDate = cal.createDateTime("20221124T063000");
+
+ const recurRule = cal.createRecurrenceRule();
+ recurRule.type = "DAILY";
+ recurRule.byCount = true;
+ recurRule.count = 5;
+
+ const recurInfo = new CalRecurrenceInfo(recurring);
+ recurInfo.appendRecurrenceItem(recurRule);
+
+ recurring.recurrenceInfo = recurInfo;
+
+ testItems.recurring = recurring;
+});
+
+add_task(async function testAddItemsAndSort() {
+ const { calendar, view } = await initializeCalendarAndView();
+
+ assertViewContainsItemsInOrder(view);
+
+ await calendar.addItem(testItems.one);
+ assertViewContainsItemsInOrder(view, "one");
+
+ await calendar.addItem(testItems.three);
+ await calendar.addItem(testItems.four);
+ assertViewContainsItemsInOrder(view, "one", "three", "four");
+
+ // Verify that items are sorted by start time by default.
+ await calendar.addItem(testItems.two);
+ assertViewContainsItemsInOrder(view, "one", "two", "three", "four");
+
+ // Change sort to ascending by title.
+ view.cycleHeader({ id: "title" });
+ assertViewContainsItemsInOrder(view, "four", "one", "three", "two");
+
+ // Verify that items are sorted appropriately on add.
+ await calendar.addItem(testItems.five);
+ assertViewContainsItemsInOrder(view, "five", "four", "one", "three", "two");
+
+ // Change sort to descending by title.
+ view.cycleHeader({ id: "title" });
+ assertViewContainsItemsInOrder(view, "two", "three", "one", "four", "five");
+
+ await calendar.addItem(testItems.six);
+ assertViewContainsItemsInOrder(view, "two", "three", "six", "one", "four", "five");
+
+ // Re-sort by start date for testing recurrences.
+ view.cycleHeader({ id: "startDate" });
+
+ // Verify that recurring events which occur more than once in the filter range
+ // show up more than once. Also verify that occurrences outside the filter
+ // range do not display.
+ await calendar.addItem(testItems.recurring);
+ assertViewContainsItemsInOrder(
+ view,
+ "one",
+ "two",
+ "three",
+ "four",
+ "five",
+ "recurring event",
+ "six",
+ "recurring event"
+ );
+
+ CalendarTestUtils.removeCalendar(calendar);
+ view.deactivate();
+});
+
+add_task(async function testInitializeWithExistingCalenderEvents() {
+ const calendar = CalendarTestUtils.createCalendar("test", "storage");
+ calendar.setProperty("calendar-main-in-composite", true);
+
+ // Add items to the calendar before we initialize the view.
+ await calendar.addItem(testItems.one);
+ await calendar.addItem(testItems.three);
+ await calendar.addItem(testItems.four);
+
+ const view = new CalendarFilteredTreeView();
+ view.startDate = cal.createDateTime("20221126");
+ view.endDate = cal.createDateTime("20221128");
+ view.itemType = Ci.calICalendar.ITEM_FILTER_TYPE_EVENT;
+
+ const tree = {
+ _batchUpdated: false,
+ _batchDepth: false,
+
+ beginUpdateBatch() {},
+ endUpdateBatch() {},
+ invalidateRow(index) {},
+ };
+ view.setTree(tree);
+
+ // Wait for the view to fetch items and update.
+ await view.activate();
+
+ // Verify that items added to the calendar before initializing are displayed.
+ assertViewContainsItemsInOrder(view, "one", "three", "four");
+
+ // Verify that adding further items causes them to be displayed as well.
+ await calendar.addItem(testItems.two);
+ assertViewContainsItemsInOrder(view, "one", "two", "three", "four");
+
+ CalendarTestUtils.removeCalendar(calendar);
+ view.deactivate();
+});
+
+add_task(async function testRemoveItems() {
+ const { calendar, view } = await initializeCalendarAndView();
+
+ // Record the calendar items so we can use them to delete.
+ const calendarItems = {};
+ for (const key in testItems) {
+ calendarItems[key] = await calendar.addItem(testItems[key]);
+ }
+
+ // Sanity check.
+ assertViewContainsItemsInOrder(
+ view,
+ "one",
+ "two",
+ "three",
+ "four",
+ "five",
+ "recurring event",
+ "six",
+ "recurring event"
+ );
+
+ await calendar.deleteItem(calendarItems.two);
+ assertViewContainsItemsInOrder(
+ view,
+ "one",
+ "three",
+ "four",
+ "five",
+ "recurring event",
+ "six",
+ "recurring event"
+ );
+
+ // Verify that all occurrences of recurring items are removed.
+ await calendar.deleteItem(calendarItems.recurring);
+ assertViewContainsItemsInOrder(view, "one", "three", "four", "five", "six");
+
+ await calendar.deleteItem(calendarItems.three);
+ await calendar.deleteItem(calendarItems.four);
+ assertViewContainsItemsInOrder(view, "one", "five", "six");
+
+ // Verify that sort order doesn't impact removal.
+ view.cycleHeader({ id: "title" });
+ await calendar.deleteItem(calendarItems.five);
+ assertViewContainsItemsInOrder(view, "one", "six");
+
+ CalendarTestUtils.removeCalendar(calendar);
+ view.deactivate();
+});
+
+add_task(async function testClearItems() {
+ const { calendar, view } = await initializeCalendarAndView();
+
+ // Add all calendar items.
+ const promises = [];
+ for (const key in testItems) {
+ promises.push(calendar.addItem(testItems[key]));
+ }
+ await Promise.all(promises);
+
+ // Sanity check.
+ assertViewContainsItemsInOrder(
+ view,
+ "one",
+ "two",
+ "three",
+ "four",
+ "five",
+ "recurring event",
+ "six",
+ "recurring event"
+ );
+
+ // Directly call clear, as there isn't a convenient way to trigger it via the
+ // calendar.
+ view.clearItems();
+
+ assertViewContainsItemsInOrder(view);
+
+ CalendarTestUtils.removeCalendar(calendar);
+ view.deactivate();
+});
+
+add_task(async function testFilterFunction() {
+ const { calendar, view } = await initializeCalendarAndView();
+
+ // Add some items which will match the filter and some which won't.
+ const promises = [];
+ for (const key of ["one", "two", "five", "recurring"]) {
+ promises.push(calendar.addItem(testItems[key]));
+ }
+ await Promise.all(promises);
+
+ // Add a selection to ensure that selections don't persist when filter changes.
+ view.selection.toggleSelect(0);
+
+ // Sanity check.
+ assertViewContainsItemsInOrder(view, "one", "two", "five", "recurring event", "recurring event");
+ Assert.ok(view.selection.isSelected(0), "item 'one' should be selected");
+
+ // Verify that setting filter function appropriately hides non-matching items.
+ view.setFilterFunction(item => {
+ return item.title.includes("f");
+ });
+ assertViewContainsItemsInOrder(view, "five");
+ Assert.ok(!view.selection.isSelected(0), "item 'five' should not be selected");
+
+ // Verify that matching items display when added.
+ await calendar.addItem(testItems.four);
+ assertViewContainsItemsInOrder(view, "four", "five");
+
+ // Verify that sorting respects filter.
+ view.cycleHeader({ id: "title" });
+ assertViewContainsItemsInOrder(view, "five", "four");
+
+ // Verify that non-matching items don't display when added.
+ await calendar.addItem(testItems.six);
+ assertViewContainsItemsInOrder(view, "five", "four");
+
+ // Verify that clearing the filter shows all items properly sorted.
+ view.clearFilter();
+ assertViewContainsItemsInOrder(
+ view,
+ "five",
+ "four",
+ "one",
+ "recurring event",
+ "recurring event",
+ "six",
+ "two"
+ );
+
+ CalendarTestUtils.removeCalendar(calendar);
+ view.deactivate();
+});
+
+add_task(async function testRemoveItemsFromCalendar() {
+ const { calendar, view } = await initializeCalendarAndView();
+
+ const secondCalendar = CalendarTestUtils.createCalendar("test", "storage");
+ secondCalendar.setProperty("calendar-main-in-composite", true);
+
+ const promises = [];
+
+ // Add some items to the first calendar.
+ for (const key of ["one", "two", "five", "recurring"]) {
+ promises.push(calendar.addItem(testItems[key]));
+ }
+
+ // Add the rest to the second calendar.
+ for (const key of ["three", "four", "six"]) {
+ promises.push(secondCalendar.addItem(testItems[key]));
+ }
+
+ await Promise.all(promises);
+
+ // Verify that both calendars are displayed.
+ assertViewContainsItemsInOrder(
+ view,
+ "one",
+ "two",
+ "three",
+ "four",
+ "five",
+ "recurring event",
+ "six",
+ "recurring event"
+ );
+
+ // Verify that removing items from a specific calendar removes exactly those
+ // events from the view.
+ view.removeItemsFromCalendar(calendar.id);
+
+ assertViewContainsItemsInOrder(view, "three", "four", "six");
+
+ CalendarTestUtils.removeCalendar(calendar);
+ CalendarTestUtils.removeCalendar(secondCalendar);
+ view.deactivate();
+});
+
+add_task(async function testSortRespectsSelection() {
+ const { calendar, view } = await initializeCalendarAndView();
+
+ // Add all calendar items.
+ const promises = [];
+ for (const key in testItems) {
+ promises.push(calendar.addItem(testItems[key]));
+ }
+ await Promise.all(promises);
+
+ view.selection.toggleSelect(1);
+ view.selection.toggleSelect(5);
+ view.selection.toggleSelect(6);
+
+ view.selection.currentIndex = 1;
+
+ // Sanity check.
+ assertViewContainsItemsInOrder(
+ view,
+ "one",
+ "two",
+ "three",
+ "four",
+ "five",
+ "recurring event",
+ "six",
+ "recurring event"
+ );
+
+ // Sanity check selection; two, recurring event, and six should be selected,
+ // nothing else.
+ Assert.ok(view.selection.isSelected(1), "item 'two' should be selected");
+ Assert.ok(view.selection.isSelected(5), "item 'recurring event' should be selected");
+ Assert.ok(view.selection.isSelected(6), "item 'three' should be selected");
+ Assert.equal(view.selection.currentIndex, 1, "item 'two' should be the current selection");
+ for (const row of [0, 2, 3, 4, 7]) {
+ Assert.ok(!view.selection.isSelected(row), `row ${row} should not be selected`);
+ }
+
+ // Verify that sorting the tree keeps the same events selected.
+ view.cycleHeader({ id: "title" });
+
+ assertViewContainsItemsInOrder(
+ view,
+ "five",
+ "four",
+ "one",
+ "recurring event",
+ "recurring event",
+ "six",
+ "three",
+ "two"
+ );
+
+ Assert.ok(view.selection.isSelected(7), "item 'two' should remain selected");
+ Assert.ok(view.selection.isSelected(5), "item 'recurring event' should remain selected");
+ Assert.ok(view.selection.isSelected(3), "item 'three' should remain selected");
+ Assert.equal(view.selection.currentIndex, 7, "item 'two' should be the current selection");
+ for (const row of [0, 1, 2, 4, 6]) {
+ Assert.ok(!view.selection.isSelected(row), `row ${row} should not be selected`);
+ }
+
+ CalendarTestUtils.removeCalendar(calendar);
+ view.deactivate();
+});
+
+function assertViewContainsItemsInOrder(view, ...expected) {
+ const actual = [];
+ for (let i = 0; i < view.rowCount; i++) {
+ actual.push(view.getCellText(i, { id: "title" }));
+ }
+
+ // Check array length. We don't use Assert.equal() here in order to provide
+ // better debugging output.
+ if (actual.length != expected.length) {
+ Assert.report(
+ actual.length != expected.length,
+ actual,
+ expected,
+ `${JSON.stringify(actual)} should have the same length as ${JSON.stringify(expected)}`
+ );
+ }
+
+ Assert.deepEqual(actual, expected);
+}
+
+async function initializeCalendarAndView() {
+ const calendar = CalendarTestUtils.createCalendar("test", "storage");
+ calendar.setProperty("calendar-main-in-composite", true);
+
+ const view = new CalendarFilteredTreeView();
+ view.startDate = cal.createDateTime("20221126");
+ view.endDate = cal.createDateTime("20221128");
+ view.itemType = Ci.calICalendar.ITEM_FILTER_TYPE_EVENT;
+ view.activate();
+
+ const tree = {
+ _batchUpdated: false,
+ _batchDepth: false,
+
+ beginUpdateBatch() {},
+ endUpdateBatch() {},
+ invalidateRow(index) {},
+ };
+ view.setTree(tree);
+
+ const selection = new TreeSelection(tree);
+ selection.view = view;
+ view.selection = selection;
+ selection.clearSelection();
+
+ return { calendar, view };
+}