diff options
Diffstat (limited to 'toolkit/components/places/tests/history/test_removeVisitsByFilter.js')
-rw-r--r-- | toolkit/components/places/tests/history/test_removeVisitsByFilter.js | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/history/test_removeVisitsByFilter.js b/toolkit/components/places/tests/history/test_removeVisitsByFilter.js new file mode 100644 index 000000000..699420e43 --- /dev/null +++ b/toolkit/components/places/tests/history/test_removeVisitsByFilter.js @@ -0,0 +1,345 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ + +// Tests for `History.removeVisitsByFilter`, as implemented in History.jsm + +"use strict"; + +Cu.importGlobalProperties(["URL"]); + +Cu.import("resource://gre/modules/PromiseUtils.jsm", this); + +add_task(function* test_removeVisitsByFilter() { + let referenceDate = new Date(1999, 9, 9, 9, 9); + + // Populate a database with 20 entries, remove a subset of entries, + // ensure consistency. + let remover = Task.async(function*(options) { + do_print("Remover with options " + JSON.stringify(options)); + let SAMPLE_SIZE = options.sampleSize; + + yield PlacesTestUtils.clearHistory(); + yield PlacesUtils.bookmarks.eraseEverything(); + + // Populate the database. + // Create `SAMPLE_SIZE` visits, from the oldest to the newest. + + let bookmarkIndices = new Set(options.bookmarks); + let visits = []; + let frecencyChangePromises = new Map(); + let uriDeletePromises = new Map(); + let getURL = options.url ? + i => "http://mozilla.com/test_browserhistory/test_removeVisitsByFilter/removeme/byurl/" + Math.floor(i / (SAMPLE_SIZE / 5)) + "/" : + i => "http://mozilla.com/test_browserhistory/test_removeVisitsByFilter/removeme/" + i + "/" + Math.random(); + for (let i = 0; i < SAMPLE_SIZE; ++i) { + let spec = getURL(i); + let uri = NetUtil.newURI(spec); + let jsDate = new Date(Number(referenceDate) + 3600 * 1000 * i); + let dbDate = jsDate * 1000; + let hasBookmark = bookmarkIndices.has(i); + let hasOwnBookmark = hasBookmark; + if (!hasOwnBookmark && options.url) { + // Also mark as bookmarked if one of the earlier bookmarked items has the same URL. + hasBookmark = + options.bookmarks.filter(n => n < i).some(n => visits[n].uri.spec == spec && visits[n].test.hasBookmark); + } + do_print("Generating " + uri.spec + ", " + dbDate); + let visit = { + uri, + title: "visit " + i, + visitDate: dbDate, + test: { + // `visitDate`, as a Date + jsDate: jsDate, + // `true` if we expect that the visit will be removed + toRemove: false, + // `true` if `onRow` informed of the removal of this visit + announcedByOnRow: false, + // `true` if there is a bookmark for this URI, i.e. of the page + // should not be entirely removed. + hasBookmark: hasBookmark, + onFrecencyChanged: null, + onDeleteURI: null, + }, + }; + visits.push(visit); + if (hasOwnBookmark) { + do_print("Adding a bookmark to visit " + i); + yield PlacesUtils.bookmarks.insert({ + url: uri, + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + title: "test bookmark" + }); + do_print("Bookmark added"); + } + } + + do_print("Adding visits"); + yield PlacesTestUtils.addVisits(visits); + + do_print("Preparing filters"); + let filter = { + }; + let beginIndex = 0; + let endIndex = visits.length - 1; + if ("begin" in options) { + let ms = Number(visits[options.begin].test.jsDate) - 1000; + filter.beginDate = new Date(ms); + beginIndex = options.begin; + } + if ("end" in options) { + let ms = Number(visits[options.end].test.jsDate) + 1000; + filter.endDate = new Date(ms); + endIndex = options.end; + } + if ("limit" in options) { + endIndex = beginIndex + options.limit - 1; // -1 because the start index is inclusive. + filter.limit = options.limit; + } + let removedItems = visits.slice(beginIndex); + endIndex -= beginIndex; + if (options.url) { + let rawURL = ""; + switch (options.url) { + case 1: + filter.url = new URL(removedItems[0].uri.spec); + rawURL = filter.url.href; + break; + case 2: + filter.url = removedItems[0].uri; + rawURL = filter.url.spec; + break; + case 3: + filter.url = removedItems[0].uri.spec; + rawURL = filter.url; + break; + } + endIndex = Math.min(endIndex, removedItems.findIndex((v, index) => v.uri.spec != rawURL) - 1); + } + removedItems.splice(endIndex + 1); + let remainingItems = visits.filter(v => !removedItems.includes(v)); + for (let i = 0; i < removedItems.length; i++) { + let test = removedItems[i].test; + do_print("Marking visit " + (beginIndex + i) + " as expecting removal"); + test.toRemove = true; + if (test.hasBookmark || + (options.url && remainingItems.some(v => v.uri.spec == removedItems[i].uri.spec))) { + frecencyChangePromises.set(removedItems[i].uri.spec, PromiseUtils.defer()); + } else if (!options.url || i == 0) { + uriDeletePromises.set(removedItems[i].uri.spec, PromiseUtils.defer()); + } + } + + let observer = { + deferred: PromiseUtils.defer(), + onBeginUpdateBatch: function() {}, + onEndUpdateBatch: function() {}, + onVisit: function(uri) { + this.deferred.reject(new Error("Unexpected call to onVisit " + uri.spec)); + }, + onTitleChanged: function(uri) { + this.deferred.reject(new Error("Unexpected call to onTitleChanged " + uri.spec)); + }, + onClearHistory: function() { + this.deferred.reject("Unexpected call to onClearHistory"); + }, + onPageChanged: function(uri) { + this.deferred.reject(new Error("Unexpected call to onPageChanged " + uri.spec)); + }, + onFrecencyChanged: function(aURI) { + do_print("onFrecencyChanged " + aURI.spec); + let deferred = frecencyChangePromises.get(aURI.spec); + Assert.ok(!!deferred, "Observing onFrecencyChanged"); + deferred.resolve(); + }, + onManyFrecenciesChanged: function() { + do_print("Many frecencies changed"); + for (let [, deferred] of frecencyChangePromises) { + deferred.resolve(); + } + }, + onDeleteURI: function(aURI) { + do_print("onDeleteURI " + aURI.spec); + let deferred = uriDeletePromises.get(aURI.spec); + Assert.ok(!!deferred, "Observing onDeleteURI"); + deferred.resolve(); + }, + onDeleteVisits: function(aURI) { + // Not sure we can test anything. + } + }; + PlacesUtils.history.addObserver(observer, false); + + let cbarg; + if (options.useCallback) { + do_print("Setting up callback"); + cbarg = [info => { + for (let visit of visits) { + do_print("Comparing " + info.date + " and " + visit.test.jsDate); + if (Math.abs(visit.test.jsDate - info.date) < 100) { // Assume rounding errors + Assert.ok(!visit.test.announcedByOnRow, + "This is the first time we announce the removal of this visit"); + Assert.ok(visit.test.toRemove, + "This is a visit we intended to remove"); + visit.test.announcedByOnRow = true; + return; + } + } + Assert.ok(false, "Could not find the visit we attempt to remove"); + }]; + } else { + do_print("No callback"); + cbarg = []; + } + let result = yield PlacesUtils.history.removeVisitsByFilter(filter, ...cbarg); + + Assert.ok(result, "Removal succeeded"); + + // Make sure that we have eliminated exactly the entries we expected + // to eliminate. + for (let i = 0; i < visits.length; ++i) { + let visit = visits[i]; + do_print("Controlling the results on visit " + i); + let remainingVisitsForURI = remainingItems.filter(v => visit.uri.spec == v.uri.spec).length; + Assert.equal( + visits_in_database(visit.uri), + remainingVisitsForURI, + "Visit is still present iff expected"); + if (options.useCallback) { + Assert.equal( + visit.test.toRemove, + visit.test.announcedByOnRow, + "Visit removal has been announced by onResult iff expected"); + } + if (visit.test.hasBookmark || remainingVisitsForURI) { + Assert.notEqual(page_in_database(visit.uri), 0, "The page should still appear in the db"); + } else { + Assert.equal(page_in_database(visit.uri), 0, "The page should have been removed from the db"); + } + } + + // Make sure that the observer has been called wherever applicable. + do_print("Checking URI delete promises."); + yield Promise.all(Array.from(uriDeletePromises.values())); + do_print("Checking frecency change promises."); + yield Promise.all(Array.from(frecencyChangePromises.values())); + PlacesUtils.history.removeObserver(observer); + }); + + let size = 20; + for (let range of [ + {begin: 0}, + {end: 19}, + {begin: 0, end: 10}, + {begin: 3, end: 4}, + {begin: 5, end: 8, limit: 2}, + {begin: 10, end: 18, limit: 5}, + ]) { + for (let bookmarks of [[], [5, 6]]) { + let options = { + sampleSize: size, + bookmarks: bookmarks, + }; + if ("begin" in range) { + options.begin = range.begin; + } + if ("end" in range) { + options.end = range.end; + } + if ("limit" in range) { + options.limit = range.limit; + } + yield remover(options); + options.url = 1; + yield remover(options); + options.url = 2; + yield remover(options); + options.url = 3; + yield remover(options); + } + } + yield PlacesTestUtils.clearHistory(); +}); + +// Test the various error cases +add_task(function* test_error_cases() { + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter(), + /TypeError: Expected a filter/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter("obviously, not a filter"), + /TypeError: Expected a filter/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({}), + /TypeError: Expected a non-empty filter/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({beginDate: "now"}), + /TypeError: Expected a Date/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({beginDate: Date.now()}), + /TypeError: Expected a Date/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({beginDate: new Date()}, "obviously, not a callback"), + /TypeError: Invalid function/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({beginDate: new Date(1000), endDate: new Date(0)}), + /TypeError: `beginDate` should be at least as old/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({limit: {}}), + /Expected a non-zero positive integer as a limit/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({limit: -1}), + /Expected a non-zero positive integer as a limit/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({limit: 0.1}), + /Expected a non-zero positive integer as a limit/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({limit: Infinity}), + /Expected a non-zero positive integer as a limit/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({url: {}}), + /Expected a valid URL for `url`/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({url: 0}), + /Expected a valid URL for `url`/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({beginDate: new Date(1000), endDate: new Date(0)}), + /TypeError: `beginDate` should be at least as old/ + ); + Assert.throws( + () => PlacesUtils.history.removeVisitsByFilter({beginDate: new Date(1000), endDate: new Date(0)}), + /TypeError: `beginDate` should be at least as old/ + ); +}); + +add_task(function* test_orphans() { + let uri = NetUtil.newURI("http://moz.org/"); + yield PlacesTestUtils.addVisits({ uri }); + + PlacesUtils.favicons.setAndFetchFaviconForPage( + uri, SMALLPNG_DATA_URI, true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, + null, Services.scriptSecurityManager.getSystemPrincipal()); + PlacesUtils.annotations.setPageAnnotation(uri, "test", "restval", 0, + PlacesUtils.annotations.EXPIRE_NEVER); + + yield PlacesUtils.history.removeVisitsByFilter({ beginDate: new Date(1999, 9, 9, 9, 9), + endDate: new Date() }); + Assert.ok(!(yield PlacesTestUtils.isPageInDB(uri)), "Page should have been removed"); + let db = yield PlacesUtils.promiseDBConnection(); + let rows = yield db.execute(`SELECT (SELECT count(*) FROM moz_annos) + + (SELECT count(*) FROM moz_favicons) AS count`); + Assert.equal(rows[0].getResultByName("count"), 0, "Should not find orphans"); +}); |