summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/tests/history/test_removeVisitsByFilter.js')
-rw-r--r--toolkit/components/places/tests/history/test_removeVisitsByFilter.js345
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");
+});