/* 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 { AppConstants } = ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs"); var { FileUtils } = ChromeUtils.importESModule("resource://gre/modules/FileUtils.sys.mjs"); var { updateAppInfo } = ChromeUtils.importESModule("resource://testing-common/AppInfo.sys.mjs"); ChromeUtils.defineModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm"); updateAppInfo(); var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); function createDate(aYear, aMonth, aDay, aHasTime, aHour, aMinute, aSecond, aTimezone) { let date = Cc["@mozilla.org/calendar/datetime;1"].createInstance(Ci.calIDateTime); date.resetTo( aYear, aMonth, aDay, aHour || 0, aMinute || 0, aSecond || 0, aTimezone || cal.dtz.UTC ); date.isDate = !aHasTime; return date; } function createEventFromIcalString(icalString) { if (/^BEGIN:VCALENDAR/.test(icalString)) { let parser = Cc["@mozilla.org/calendar/ics-parser;1"].createInstance(Ci.calIIcsParser); parser.parseString(icalString); let items = parser.getItems(); cal.ASSERT(items.length == 1); return items[0].QueryInterface(Ci.calIEvent); } let event = Cc["@mozilla.org/calendar/event;1"].createInstance(Ci.calIEvent); event.icalString = icalString; return event; } function createTodoFromIcalString(icalString) { let todo = Cc["@mozilla.org/calendar/todo;1"].createInstance(Ci.calITodo); todo.icalString = icalString; return todo; } function getMemoryCal() { return Cc["@mozilla.org/calendar/calendar;1?type=memory"].createInstance( Ci.calISyncWriteCalendar ); } function getStorageCal() { // Whenever we get the storage calendar we need to request a profile, // otherwise the cleanup functions will not run do_get_profile(); // create URI let db = Services.dirsvc.get("TmpD", Ci.nsIFile); db.append("test_storage.sqlite"); let uri = Services.io.newFileURI(db); // Make sure timezone service is initialized Cc["@mozilla.org/calendar/timezone-service;1"].getService(Ci.calIStartupService).startup(null); // create storage calendar let stor = Cc["@mozilla.org/calendar/calendar;1?type=storage"].createInstance( Ci.calISyncWriteCalendar ); stor.uri = uri; stor.id = cal.getUUID(); return stor; } /** * Return an item property as string. * * @param aItem * @param string aProp possible item properties: start, end, duration, * generation, title, * id, calendar, creationDate, lastModifiedTime, * stampTime, priority, privacy, status, * alarmLastAck, recurrenceStartDate * and any property that can be obtained using getProperty() */ function getProps(aItem, aProp) { let value = null; switch (aProp) { case "start": value = aItem.startDate || aItem.entryDate || null; break; case "end": value = aItem.endDate || aItem.dueDate || null; break; case "duration": value = aItem.duration || null; break; case "generation": value = aItem.generation; break; case "title": value = aItem.title; break; case "id": value = aItem.id; break; case "calendar": value = aItem.calendar.id; break; case "creationDate": value = aItem.creationDate; break; case "lastModifiedTime": value = aItem.lastModifiedTime; break; case "stampTime": value = aItem.stampTime; break; case "priority": value = aItem.priority; break; case "privacy": value = aItem.privacy; break; case "status": value = aItem.status; break; case "alarmLastAck": value = aItem.alarmLastAck; break; case "recurrenceStartDate": value = aItem.recurrenceStartDate; break; default: value = aItem.getProperty(aProp); } if (value) { return value.toString(); } return null; } function compareItemsSpecific(aLeftItem, aRightItem, aPropArray) { if (!aPropArray) { // left out: "id", "calendar", "lastModifiedTime", "generation", // "stampTime" as these are expected to change aPropArray = [ "start", "end", "duration", "title", "priority", "privacy", "creationDate", "status", "alarmLastAck", "recurrenceStartDate", ]; } if (aLeftItem instanceof Ci.calIEvent) { aLeftItem.QueryInterface(Ci.calIEvent); } else if (aLeftItem instanceof Ci.calITodo) { aLeftItem.QueryInterface(Ci.calITodo); } for (let i = 0; i < aPropArray.length; i++) { equal(getProps(aLeftItem, aPropArray[i]), getProps(aRightItem, aPropArray[i])); } } /** * Unfold ics lines by removing any \r\n or \n followed by a linear whitespace * (space or htab). * * @param aLine The line to unfold * @returns The unfolded line */ function ics_unfoldline(aLine) { return aLine.replace(/\r?\n[ \t]/g, ""); } /** * Dedent the template string tagged with this function to make indented data * easier to read. Usage: * * let data = dedent` * This is indented data it will be unindented so that the first line has * no leading spaces and the second is indented by two spaces. * `; * * @param strings The string fragments from the template string * @param ...values The interpolated values * @returns The interpolated, dedented string */ function dedent(strings, ...values) { let parts = []; // Perform variable interpolation let minIndent = Infinity; for (let [i, string] of strings.entries()) { let innerparts = string.split("\n"); if (i == 0) { innerparts.shift(); } if (i == strings.length - 1) { innerparts.pop(); } for (let [j, ip] of innerparts.entries()) { let match = ip.match(/^(\s*)\S*/); if (j != 0) { minIndent = Math.min(minIndent, match[1].length); } } parts.push(innerparts); } return parts .map((part, i) => { return ( part .map((line, j) => { return j == 0 && i > 0 ? line : line.substr(minIndent); }) .join("\n") + (i < values.length ? values[i] : "") ); }) .join(""); } /** * Read a JSON file and return the JS object */ function readJSONFile(aFile) { let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); try { stream.init(aFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); let bytes = NetUtil.readInputStream(stream, stream.available()); let data = JSON.parse(new TextDecoder().decode(bytes)); return data; } catch (ex) { dump("readJSONFile: Error reading JSON file: " + ex); } finally { stream.close(); } return false; } function do_load_timezoneservice(callback) { do_test_pending(); cal.timezoneService.startup({ onResult() { do_test_finished(); callback(); }, }); } function do_load_calmgr(callback) { do_test_pending(); cal.manager.startup({ onResult() { do_test_finished(); callback(); }, }); } function do_calendar_startup(callback) { let obs = { observe() { Services.obs.removeObserver(this, "calendar-startup-done"); do_test_finished(); executeSoon(callback); }, }; let startupService = Cc["@mozilla.org/calendar/startup-service;1"].getService( Ci.nsISupports ).wrappedJSObject; if (startupService.started) { callback(); } else { do_test_pending(); Services.obs.addObserver(obs, "calendar-startup-done"); if (this._profileInitialized) { Services.obs.notifyObservers(null, "profile-after-change", "xpcshell-do-get-profile"); } else { do_get_profile(true); } } } /** * Monkey patch the function with the name x on obj and overwrite it with func. * The first parameter of this function is the original function that can be * called at any time. * * @param obj The object the function is on. * @param name The string name of the function. * @param func The function to monkey patch with. */ function monkeyPatch(obj, x, func) { let old = obj[x]; obj[x] = function () { let parent = old.bind(obj); let args = Array.from(arguments); args.unshift(parent); try { return func.apply(obj, args); } catch (e) { console.error(e); throw e; } }; } /** * Asserts the properties of an actual extract parser result to what was * expected. * * @param {object} actual - Mostly the actual output of parse(). * @param {object} expected - The expected output. * @param {string} level - The variable name to refer to report on. */ function compareExtractResults(actual, expected, level = "") { for (let [key, value] of Object.entries(expected)) { let qualifiedKey = [level, Array.isArray(expected) ? `[${key}]` : `.${key}`].join(""); if (value && typeof value == "object") { Assert.ok(actual[key], `${qualifiedKey} is not null`); compareExtractResults(actual[key], value, qualifiedKey); continue; } Assert.equal(actual[key], value, `${qualifiedKey} has value "${value}"`); } }