diff options
Diffstat (limited to 'comm/calendar/test/unit/test_filter_tree_view.js')
-rw-r--r-- | comm/calendar/test/unit/test_filter_tree_view.js | 451 |
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 }; +} |