diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/components/places/tests/history | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/places/tests/history')
8 files changed, 1365 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/history/.eslintrc.js b/toolkit/components/places/tests/history/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/toolkit/components/places/tests/history/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/components/places/tests/history/head_history.js b/toolkit/components/places/tests/history/head_history.js new file mode 100644 index 000000000..870802dc1 --- /dev/null +++ b/toolkit/components/places/tests/history/head_history.js @@ -0,0 +1,19 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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 Ci = Components.interfaces; +var Cc = Components.classes; +var Cr = Components.results; +var Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); + +// Import common head. +{ + let commonFile = do_get_file("../head_common.js", false); + let uri = Services.io.newFileURI(commonFile); + Services.scriptloader.loadSubScript(uri.spec, this); +} diff --git a/toolkit/components/places/tests/history/test_insert.js b/toolkit/components/places/tests/history/test_insert.js new file mode 100644 index 000000000..e2884af8c --- /dev/null +++ b/toolkit/components/places/tests/history/test_insert.js @@ -0,0 +1,257 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ + +// Tests for `History.insert` and `History.insertMany`, as implemented in History.jsm + +"use strict"; + +add_task(function* test_insert_error_cases() { + const TEST_URL = "http://mozilla.com"; + + Assert.throws( + () => PlacesUtils.history.insert(), + /TypeError: pageInfo must be an object/, + "passing a null into History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert(1), + /TypeError: pageInfo must be an object/, + "passing a non object into History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({}), + /TypeError: PageInfo object must have a url property/, + "passing an object without a url to History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({url: 123}), + /TypeError: Invalid url or guid: 123/, + "passing an object with an invalid url to History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({url: TEST_URL}), + /TypeError: PageInfo object must have an array of visits/, + "passing an object without a visits property to History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({url: TEST_URL, visits: 1}), + /TypeError: PageInfo object must have an array of visits/, + "passing an object with a non-array visits property to History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({url: TEST_URL, visits: []}), + /TypeError: PageInfo object must have an array of visits/, + "passing an object with an empty array as the visits property to History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({ + url: TEST_URL, + visits: [ + { + transition: TRANSITION_LINK, + date: "a" + } + ]}), + /TypeError: Expected a Date, got a/, + "passing a visit object with an invalid date to History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({ + url: TEST_URL, + visits: [ + { + transition: TRANSITION_LINK + }, + { + transition: TRANSITION_LINK, + date: "a" + } + ]}), + /TypeError: Expected a Date, got a/, + "passing a second visit object with an invalid date to History.insert should throw a TypeError" + ); + let futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 1000); + Assert.throws( + () => PlacesUtils.history.insert({ + url: TEST_URL, + visits: [ + { + transition: TRANSITION_LINK, + date: futureDate, + } + ]}), + `TypeError: date: ${futureDate} is not a valid date`, + "passing a visit object with a future date to History.insert should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insert({ + url: TEST_URL, + visits: [ + {transition: "a"} + ]}), + /TypeError: transition: a is not a valid transition type/, + "passing a visit object with an invalid transition to History.insert should throw a TypeError" + ); +}); + +add_task(function* test_history_insert() { + const TEST_URL = "http://mozilla.com/"; + + let inserter = Task.async(function*(name, filter, referrer, date, transition) { + do_print(name); + do_print(`filter: ${filter}, referrer: ${referrer}, date: ${date}, transition: ${transition}`); + + let uri = NetUtil.newURI(TEST_URL + Math.random()); + let title = "Visit " + Math.random(); + + let pageInfo = { + title, + visits: [ + {transition: transition, referrer: referrer, date: date, } + ] + }; + + pageInfo.url = yield filter(uri); + + let result = yield PlacesUtils.history.insert(pageInfo); + + Assert.ok(PlacesUtils.isValidGuid(result.guid), "guid for pageInfo object is valid"); + Assert.equal(uri.spec, result.url.href, "url is correct for pageInfo object"); + Assert.equal(title, result.title, "title is correct for pageInfo object"); + Assert.equal(TRANSITION_LINK, result.visits[0].transition, "transition is correct for pageInfo object"); + if (referrer) { + Assert.equal(referrer, result.visits[0].referrer.href, "url of referrer for visit is correct"); + } else { + Assert.equal(null, result.visits[0].referrer, "url of referrer for visit is correct"); + } + if (date) { + Assert.equal(Number(date), + Number(result.visits[0].date), + "date of visit is correct"); + } + + Assert.ok(yield PlacesTestUtils.isPageInDB(uri), "Page was added"); + Assert.ok(yield PlacesTestUtils.visitsInDB(uri), "Visit was added"); + }); + + try { + for (let referrer of [TEST_URL, null]) { + for (let date of [new Date(), null]) { + for (let transition of [TRANSITION_LINK, null]) { + yield inserter("Testing History.insert() with an nsIURI", x => x, referrer, date, transition); + yield inserter("Testing History.insert() with a string url", x => x.spec, referrer, date, transition); + yield inserter("Testing History.insert() with a URL object", x => new URL(x.spec), referrer, date, transition); + } + } + } + } finally { + yield PlacesTestUtils.clearHistory(); + } +}); + +add_task(function* test_insert_multiple_error_cases() { + let validPageInfo = { + url: "http://mozilla.com", + visits: [ + {transition: TRANSITION_LINK} + ] + }; + + Assert.throws( + () => PlacesUtils.history.insertMany(), + /TypeError: pageInfos must be an array/, + "passing a null into History.insertMany should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insertMany([]), + /TypeError: pageInfos may not be an empty array/, + "passing an empty array into History.insertMany should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.insertMany([validPageInfo, {}]), + /TypeError: PageInfo object must have a url property/, + "passing a second invalid PageInfo object to History.insertMany should throw a TypeError" + ); +}); + +add_task(function* test_history_insertMany() { + const BAD_URLS = ["about:config", "chrome://browser/content/browser.xul"]; + const GOOD_URLS = [1, 2, 3].map(x => { return `http://mozilla.com/${x}`; }); + + let makePageInfos = Task.async(function*(urls, filter = x => x) { + let pageInfos = []; + for (let url of urls) { + let uri = NetUtil.newURI(url); + + let pageInfo = { + title: `Visit to ${url}`, + visits: [ + {transition: TRANSITION_LINK} + ] + }; + + pageInfo.url = yield filter(uri); + pageInfos.push(pageInfo); + } + return pageInfos; + }); + + let inserter = Task.async(function*(name, filter, useCallbacks) { + do_print(name); + do_print(`filter: ${filter}`); + do_print(`useCallbacks: ${useCallbacks}`); + yield PlacesTestUtils.clearHistory(); + + let result; + let allUrls = GOOD_URLS.concat(BAD_URLS); + let pageInfos = yield makePageInfos(allUrls, filter); + + if (useCallbacks) { + let onResultUrls = []; + let onErrorUrls = []; + result = yield PlacesUtils.history.insertMany(pageInfos, pageInfo => { + let url = pageInfo.url.href; + Assert.ok(GOOD_URLS.includes(url), "onResult callback called for correct url"); + onResultUrls.push(url); + Assert.equal(`Visit to ${url}`, pageInfo.title, "onResult callback provides the correct title"); + Assert.ok(PlacesUtils.isValidGuid(pageInfo.guid), "onResult callback provides a valid guid"); + }, pageInfo => { + let url = pageInfo.url.href; + Assert.ok(BAD_URLS.includes(url), "onError callback called for correct uri"); + onErrorUrls.push(url); + Assert.equal(undefined, pageInfo.title, "onError callback provides the correct title"); + Assert.equal(undefined, pageInfo.guid, "onError callback provides the expected guid"); + }); + Assert.equal(GOOD_URLS.sort().toString(), onResultUrls.sort().toString(), "onResult callback was called for each good url"); + Assert.equal(BAD_URLS.sort().toString(), onErrorUrls.sort().toString(), "onError callback was called for each bad url"); + } else { + result = yield PlacesUtils.history.insertMany(pageInfos); + } + + Assert.equal(undefined, result, "insertMany returned undefined"); + + for (let url of allUrls) { + let expected = GOOD_URLS.includes(url); + Assert.equal(expected, yield PlacesTestUtils.isPageInDB(url), `isPageInDB for ${url} is ${expected}`); + Assert.equal(expected, yield PlacesTestUtils.visitsInDB(url), `visitsInDB for ${url} is ${expected}`); + } + }); + + try { + for (let useCallbacks of [false, true]) { + yield inserter("Testing History.insertMany() with an nsIURI", x => x, useCallbacks); + yield inserter("Testing History.insertMany() with a string url", x => x.spec, useCallbacks); + yield inserter("Testing History.insertMany() with a URL object", x => new URL(x.spec), useCallbacks); + } + // Test rejection when no items added + let pageInfos = yield makePageInfos(BAD_URLS); + PlacesUtils.history.insertMany(pageInfos).then(() => { + Assert.ok(false, "History.insertMany rejected promise with all bad URLs"); + }, error => { + Assert.equal("No items were added to history.", error.message, "History.insertMany rejected promise with all bad URLs"); + }); + } finally { + yield PlacesTestUtils.clearHistory(); + } +}); diff --git a/toolkit/components/places/tests/history/test_remove.js b/toolkit/components/places/tests/history/test_remove.js new file mode 100644 index 000000000..7423f6464 --- /dev/null +++ b/toolkit/components/places/tests/history/test_remove.js @@ -0,0 +1,360 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ + +// Tests for `History.remove`, as implemented in History.jsm + +"use strict"; + +Cu.importGlobalProperties(["URL"]); + + +// Test removing a single page +add_task(function* test_remove_single() { + yield PlacesTestUtils.clearHistory(); + yield PlacesUtils.bookmarks.eraseEverything(); + + + let WITNESS_URI = NetUtil.newURI("http://mozilla.com/test_browserhistory/test_remove/" + Math.random()); + yield PlacesTestUtils.addVisits(WITNESS_URI); + Assert.ok(page_in_database(WITNESS_URI)); + + let remover = Task.async(function*(name, filter, options) { + do_print(name); + do_print(JSON.stringify(options)); + do_print("Setting up visit"); + + let uri = NetUtil.newURI("http://mozilla.com/test_browserhistory/test_remove/" + Math.random()); + let title = "Visit " + Math.random(); + yield PlacesTestUtils.addVisits({uri: uri, title: title}); + Assert.ok(visits_in_database(uri), "History entry created"); + + let removeArg = yield filter(uri); + + if (options.addBookmark) { + PlacesUtils.bookmarks.insertBookmark( + PlacesUtils.unfiledBookmarksFolderId, + uri, + PlacesUtils.bookmarks.DEFAULT_INDEX, + "test bookmark"); + } + + let shouldRemove = !options.addBookmark; + let observer; + let promiseObserved = new Promise((resolve, reject) => { + observer = { + onBeginUpdateBatch: function() {}, + onEndUpdateBatch: function() {}, + onVisit: function(aUri) { + reject(new Error("Unexpected call to onVisit " + aUri.spec)); + }, + onTitleChanged: function(aUri) { + reject(new Error("Unexpected call to onTitleChanged " + aUri.spec)); + }, + onClearHistory: function() { + reject("Unexpected call to onClearHistory"); + }, + onPageChanged: function(aUri) { + reject(new Error("Unexpected call to onPageChanged " + aUri.spec)); + }, + onFrecencyChanged: function(aURI) { + try { + Assert.ok(!shouldRemove, "Observing onFrecencyChanged"); + Assert.equal(aURI.spec, uri.spec, "Observing effect on the right uri"); + } finally { + resolve(); + } + }, + onManyFrecenciesChanged: function() { + try { + Assert.ok(!shouldRemove, "Observing onManyFrecenciesChanged"); + } finally { + resolve(); + } + }, + onDeleteURI: function(aURI) { + try { + Assert.ok(shouldRemove, "Observing onDeleteURI"); + Assert.equal(aURI.spec, uri.spec, "Observing effect on the right uri"); + } finally { + resolve(); + } + }, + onDeleteVisits: function(aURI) { + Assert.equal(aURI.spec, uri.spec, "Observing onDeleteVisits on the right uri"); + } + }; + }); + PlacesUtils.history.addObserver(observer, false); + + do_print("Performing removal"); + let removed = false; + if (options.useCallback) { + let onRowCalled = false; + let guid = do_get_guid_for_uri(uri); + removed = yield PlacesUtils.history.remove(removeArg, page => { + Assert.equal(onRowCalled, false, "Callback has not been called yet"); + onRowCalled = true; + Assert.equal(page.url.href, uri.spec, "Callback provides the correct url"); + Assert.equal(page.guid, guid, "Callback provides the correct guid"); + Assert.equal(page.title, title, "Callback provides the correct title"); + }); + Assert.ok(onRowCalled, "Callback has been called"); + } else { + removed = yield PlacesUtils.history.remove(removeArg); + } + + yield promiseObserved; + PlacesUtils.history.removeObserver(observer); + + Assert.equal(visits_in_database(uri), 0, "History entry has disappeared"); + Assert.notEqual(visits_in_database(WITNESS_URI), 0, "Witness URI still has visits"); + Assert.notEqual(page_in_database(WITNESS_URI), 0, "Witness URI is still here"); + if (shouldRemove) { + Assert.ok(removed, "Something was removed"); + Assert.equal(page_in_database(uri), 0, "Page has disappeared"); + } else { + Assert.ok(!removed, "The page was not removed, as there was a bookmark"); + Assert.notEqual(page_in_database(uri), 0, "The page is still present"); + } + }); + + try { + for (let useCallback of [false, true]) { + for (let addBookmark of [false, true]) { + let options = { useCallback: useCallback, addBookmark: addBookmark }; + yield remover("Testing History.remove() with a single URI", x => x, options); + yield remover("Testing History.remove() with a single string url", x => x.spec, options); + yield remover("Testing History.remove() with a single string guid", x => do_get_guid_for_uri(x), options); + yield remover("Testing History.remove() with a single URI in an array", x => [x], options); + yield remover("Testing History.remove() with a single string url in an array", x => [x.spec], options); + yield remover("Testing History.remove() with a single string guid in an array", x => [do_get_guid_for_uri(x)], options); + } + } + } finally { + yield PlacesTestUtils.clearHistory(); + } + return; +}); + +// Test removing a list of pages +add_task(function* test_remove_many() { + const SIZE = 10; + + yield PlacesTestUtils.clearHistory(); + yield PlacesUtils.bookmarks.eraseEverything(); + + do_print("Adding a witness page"); + let WITNESS_URI = NetUtil.newURI("http://mozilla.com/test_browserhistory/test_remove/" + Math.random()); + yield PlacesTestUtils.addVisits(WITNESS_URI); + Assert.ok(page_in_database(WITNESS_URI), "Witness page added"); + + do_print("Generating samples"); + let pages = []; + for (let i = 0; i < SIZE; ++i) { + let uri = NetUtil.newURI("http://mozilla.com/test_browserhistory/test_remove?sample=" + i + "&salt=" + Math.random()); + let title = "Visit " + i + ", " + Math.random(); + let hasBookmark = i % 3 == 0; + let page = { + uri: uri, + title: title, + hasBookmark: hasBookmark, + // `true` once `onResult` has been called for this page + onResultCalled: false, + // `true` once `onDeleteVisits` has been called for this page + onDeleteVisitsCalled: false, + // `true` once `onFrecencyChangedCalled` has been called for this page + onFrecencyChangedCalled: false, + // `true` once `onDeleteURI` has been called for this page + onDeleteURICalled: false, + }; + do_print("Pushing: " + uri.spec); + pages.push(page); + + yield PlacesTestUtils.addVisits(page); + page.guid = do_get_guid_for_uri(uri); + if (hasBookmark) { + PlacesUtils.bookmarks.insertBookmark( + PlacesUtils.unfiledBookmarksFolderId, + uri, + PlacesUtils.bookmarks.DEFAULT_INDEX, + "test bookmark " + i); + } + Assert.ok(page_in_database(uri), "Page added"); + } + + do_print("Mixing key types and introducing dangling keys"); + let keys = []; + for (let i = 0; i < SIZE; ++i) { + if (i % 4 == 0) { + keys.push(pages[i].uri); + keys.push(NetUtil.newURI("http://example.org/dangling/nsIURI/" + i)); + } else if (i % 4 == 1) { + keys.push(new URL(pages[i].uri.spec)); + keys.push(new URL("http://example.org/dangling/URL/" + i)); + } else if (i % 4 == 2) { + keys.push(pages[i].uri.spec); + keys.push("http://example.org/dangling/stringuri/" + i); + } else { + keys.push(pages[i].guid); + keys.push(("guid_" + i + "_01234567890").substr(0, 12)); + } + } + + let observer = { + onBeginUpdateBatch: function() {}, + onEndUpdateBatch: function() {}, + onVisit: function(aURI) { + Assert.ok(false, "Unexpected call to onVisit " + aURI.spec); + }, + onTitleChanged: function(aURI) { + Assert.ok(false, "Unexpected call to onTitleChanged " + aURI.spec); + }, + onClearHistory: function() { + Assert.ok(false, "Unexpected call to onClearHistory"); + }, + onPageChanged: function(aURI) { + Assert.ok(false, "Unexpected call to onPageChanged " + aURI.spec); + }, + onFrecencyChanged: function(aURI) { + let origin = pages.find(x => x.uri.spec == aURI.spec); + Assert.ok(origin); + Assert.ok(origin.hasBookmark, "Observing onFrecencyChanged on a page with a bookmark"); + origin.onFrecencyChangedCalled = true; + // We do not make sure that `origin.onFrecencyChangedCalled` is `false`, as + }, + onManyFrecenciesChanged: function() { + Assert.ok(false, "Observing onManyFrecenciesChanges, this is most likely correct but not covered by this test"); + }, + onDeleteURI: function(aURI) { + let origin = pages.find(x => x.uri.spec == aURI.spec); + Assert.ok(origin); + Assert.ok(!origin.hasBookmark, "Observing onDeleteURI on a page without a bookmark"); + Assert.ok(!origin.onDeleteURICalled, "Observing onDeleteURI for the first time"); + origin.onDeleteURICalled = true; + }, + onDeleteVisits: function(aURI) { + let origin = pages.find(x => x.uri.spec == aURI.spec); + Assert.ok(origin); + Assert.ok(!origin.onDeleteVisitsCalled, "Observing onDeleteVisits for the first time"); + origin.onDeleteVisitsCalled = true; + } + }; + PlacesUtils.history.addObserver(observer, false); + + do_print("Removing the pages and checking the callbacks"); + let removed = yield PlacesUtils.history.remove(keys, page => { + let origin = pages.find(candidate => candidate.uri.spec == page.url.href); + + Assert.ok(origin, "onResult has a valid page"); + Assert.ok(!origin.onResultCalled, "onResult has not seen this page yet"); + origin.onResultCalled = true; + Assert.equal(page.guid, origin.guid, "onResult has the right guid"); + Assert.equal(page.title, origin.title, "onResult has the right title"); + }); + Assert.ok(removed, "Something was removed"); + + PlacesUtils.history.removeObserver(observer); + + do_print("Checking out results"); + // By now the observers should have been called. + for (let i = 0; i < pages.length; ++i) { + let page = pages[i]; + do_print("Page: " + i); + Assert.ok(page.onResultCalled, "We have reached the page from the callback"); + Assert.ok(visits_in_database(page.uri) == 0, "History entry has disappeared"); + Assert.equal(page_in_database(page.uri) != 0, page.hasBookmark, "Page is present only if it also has bookmarks"); + Assert.equal(page.onFrecencyChangedCalled, page.onDeleteVisitsCalled, "onDeleteVisits was called iff onFrecencyChanged was called"); + Assert.ok(page.onFrecencyChangedCalled ^ page.onDeleteURICalled, "Either onFrecencyChanged or onDeleteURI was called"); + } + + Assert.notEqual(visits_in_database(WITNESS_URI), 0, "Witness URI still has visits"); + Assert.notEqual(page_in_database(WITNESS_URI), 0, "Witness URI is still here"); +}); + +add_task(function* cleanup() { + yield PlacesTestUtils.clearHistory(); + yield PlacesUtils.bookmarks.eraseEverything(); +}); + +// Test the various error cases +add_task(function* test_error_cases() { + Assert.throws( + () => PlacesUtils.history.remove(), + /TypeError: Invalid url/, + "History.remove with no argument should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove(null), + /TypeError: Invalid url/, + "History.remove with `null` should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove(undefined), + /TypeError: Invalid url/, + "History.remove with `undefined` should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove("not a guid, obviously"), + /TypeError: .* is not a valid URL/, + "History.remove with an ill-formed guid/url argument should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove({"not the kind of object we know how to handle": true}), + /TypeError: Invalid url/, + "History.remove with an unexpected object should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove([]), + /TypeError: Expected at least one page/, + "History.remove with an empty array should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove([null]), + /TypeError: Invalid url or guid/, + "History.remove with an array containing null should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove(["http://example.org", "not a guid, obviously"]), + /TypeError: .* is not a valid URL/, + "History.remove with an array containing an ill-formed guid/url argument should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove(["0123456789ab"/* valid guid*/, null]), + /TypeError: Invalid url or guid: null/, + "History.remove with an array containing a guid and a second argument that is null should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove(["http://example.org", {"not the kind of object we know how to handle": true}]), + /TypeError: Invalid url/, + "History.remove with an array containing an unexpected objecgt should throw a TypeError" + ); + Assert.throws( + () => PlacesUtils.history.remove("http://example.org", "not a function, obviously"), + /TypeError: Invalid function/, + "History.remove with a second argument that is not a function argument should throw a TypeError" + ); + try { + PlacesUtils.history.remove("http://example.org/I/have/clearly/not/been/added", null); + Assert.ok(true, "History.remove should ignore `null` as a second argument"); + } catch (ex) { + Assert.ok(false, "History.remove should ignore `null` as a second argument"); + } +}); + +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.remove(uri); + 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"); +}); diff --git a/toolkit/components/places/tests/history/test_removeVisits.js b/toolkit/components/places/tests/history/test_removeVisits.js new file mode 100644 index 000000000..8df0c81a9 --- /dev/null +++ b/toolkit/components/places/tests/history/test_removeVisits.js @@ -0,0 +1,316 @@ +const JS_NOW = Date.now(); +const DB_NOW = JS_NOW * 1000; +const TEST_URI = uri("http://example.com/"); +const PLACE_URI = uri("place:queryType=0&sort=8&maxResults=10"); + +function* cleanup() { + yield PlacesTestUtils.clearHistory(); + yield PlacesUtils.bookmarks.eraseEverything(); + // This is needed to remove place: entries. + DBConn().executeSimpleSQL("DELETE FROM moz_places"); +} + +add_task(function* remove_visits_outside_unbookmarked_uri() { + do_print("*** TEST: Remove some visits outside valid timeframe from an unbookmarked URI"); + + do_print("Add 10 visits for the URI from way in the past."); + let visits = []; + for (let i = 0; i < 10; i++) { + visits.push({ uri: TEST_URI, visitDate: DB_NOW - 100000 - (i * 1000) }); + } + yield PlacesTestUtils.addVisits(visits); + + do_print("Remove visits using timerange outside the URI's visits."); + let filter = { + beginDate: new Date(JS_NOW - 10), + endDate: new Date(JS_NOW) + }; + yield PlacesUtils.history.removeVisitsByFilter(filter); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("URI should still exist in moz_places."); + do_check_true(page_in_database(TEST_URI.spec)); + + do_print("Run a history query and check that all visits still exist."); + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + opts.resultType = opts.RESULTS_AS_VISIT; + opts.sortingMode = opts.SORT_BY_DATE_DESCENDING; + let root = PlacesUtils.history.executeQuery(query, opts).root; + root.containerOpen = true; + do_check_eq(root.childCount, 10); + for (let i = 0; i < root.childCount; i++) { + let visitTime = root.getChild(i).time; + do_check_eq(visitTime, DB_NOW - 100000 - (i * 1000)); + } + root.containerOpen = false; + + do_print("asyncHistory.isURIVisited should return true."); + do_check_true(yield promiseIsURIVisited(TEST_URI)); + + yield PlacesTestUtils.promiseAsyncUpdates(); + do_print("Frecency should be positive.") + do_check_true(frecencyForUrl(TEST_URI) > 0); + + yield cleanup(); +}); + +add_task(function* remove_visits_outside_bookmarked_uri() { + do_print("*** TEST: Remove some visits outside valid timeframe from a bookmarked URI"); + + do_print("Add 10 visits for the URI from way in the past."); + let visits = []; + for (let i = 0; i < 10; i++) { + visits.push({ uri: TEST_URI, visitDate: DB_NOW - 100000 - (i * 1000) }); + } + yield PlacesTestUtils.addVisits(visits); + do_print("Bookmark the URI."); + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + TEST_URI, + PlacesUtils.bookmarks.DEFAULT_INDEX, + "bookmark title"); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("Remove visits using timerange outside the URI's visits."); + let filter = { + beginDate: new Date(JS_NOW - 10), + endDate: new Date(JS_NOW) + }; + yield PlacesUtils.history.removeVisitsByFilter(filter); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("URI should still exist in moz_places."); + do_check_true(page_in_database(TEST_URI.spec)); + + do_print("Run a history query and check that all visits still exist."); + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + opts.resultType = opts.RESULTS_AS_VISIT; + opts.sortingMode = opts.SORT_BY_DATE_DESCENDING; + let root = PlacesUtils.history.executeQuery(query, opts).root; + root.containerOpen = true; + do_check_eq(root.childCount, 10); + for (let i = 0; i < root.childCount; i++) { + let visitTime = root.getChild(i).time; + do_check_eq(visitTime, DB_NOW - 100000 - (i * 1000)); + } + root.containerOpen = false; + + do_print("asyncHistory.isURIVisited should return true."); + do_check_true(yield promiseIsURIVisited(TEST_URI)); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("Frecency should be positive.") + do_check_true(frecencyForUrl(TEST_URI) > 0); + + yield cleanup(); +}); + +add_task(function* remove_visits_unbookmarked_uri() { + do_print("*** TEST: Remove some visits from an unbookmarked URI"); + + do_print("Add 10 visits for the URI from now to 9 usecs in the past."); + let visits = []; + for (let i = 0; i < 10; i++) { + visits.push({ uri: TEST_URI, visitDate: DB_NOW - (i * 1000) }); + } + yield PlacesTestUtils.addVisits(visits); + + do_print("Remove the 5 most recent visits."); + let filter = { + beginDate: new Date(JS_NOW - 4), + endDate: new Date(JS_NOW) + }; + yield PlacesUtils.history.removeVisitsByFilter(filter); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("URI should still exist in moz_places."); + do_check_true(page_in_database(TEST_URI.spec)); + + do_print("Run a history query and check that only the older 5 visits still exist."); + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + opts.resultType = opts.RESULTS_AS_VISIT; + opts.sortingMode = opts.SORT_BY_DATE_DESCENDING; + let root = PlacesUtils.history.executeQuery(query, opts).root; + root.containerOpen = true; + do_check_eq(root.childCount, 5); + for (let i = 0; i < root.childCount; i++) { + let visitTime = root.getChild(i).time; + do_check_eq(visitTime, DB_NOW - (i * 1000) - 5000); + } + root.containerOpen = false; + + do_print("asyncHistory.isURIVisited should return true."); + do_check_true(yield promiseIsURIVisited(TEST_URI)); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("Frecency should be positive.") + do_check_true(frecencyForUrl(TEST_URI) > 0); + + yield cleanup(); +}); + +add_task(function* remove_visits_bookmarked_uri() { + do_print("*** TEST: Remove some visits from a bookmarked URI"); + + do_print("Add 10 visits for the URI from now to 9 usecs in the past."); + let visits = []; + for (let i = 0; i < 10; i++) { + visits.push({ uri: TEST_URI, visitDate: DB_NOW - (i * 1000) }); + } + yield PlacesTestUtils.addVisits(visits); + do_print("Bookmark the URI."); + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + TEST_URI, + PlacesUtils.bookmarks.DEFAULT_INDEX, + "bookmark title"); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("Remove the 5 most recent visits."); + let filter = { + beginDate: new Date(JS_NOW - 4), + endDate: new Date(JS_NOW) + }; + yield PlacesUtils.history.removeVisitsByFilter(filter); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("URI should still exist in moz_places."); + do_check_true(page_in_database(TEST_URI.spec)); + + do_print("Run a history query and check that only the older 5 visits still exist."); + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + opts.resultType = opts.RESULTS_AS_VISIT; + opts.sortingMode = opts.SORT_BY_DATE_DESCENDING; + let root = PlacesUtils.history.executeQuery(query, opts).root; + root.containerOpen = true; + do_check_eq(root.childCount, 5); + for (let i = 0; i < root.childCount; i++) { + let visitTime = root.getChild(i).time; + do_check_eq(visitTime, DB_NOW - (i * 1000) - 5000); + } + root.containerOpen = false; + + do_print("asyncHistory.isURIVisited should return true."); + do_check_true(yield promiseIsURIVisited(TEST_URI)); + yield PlacesTestUtils.promiseAsyncUpdates() + + do_print("Frecency should be positive.") + do_check_true(frecencyForUrl(TEST_URI) > 0); + + yield cleanup(); +}); + +add_task(function* remove_all_visits_unbookmarked_uri() { + do_print("*** TEST: Remove all visits from an unbookmarked URI"); + + do_print("Add some visits for the URI."); + let visits = []; + for (let i = 0; i < 10; i++) { + visits.push({ uri: TEST_URI, visitDate: DB_NOW - (i * 1000) }); + } + yield PlacesTestUtils.addVisits(visits); + + do_print("Remove all visits."); + let filter = { + beginDate: new Date(JS_NOW - 10), + endDate: new Date(JS_NOW) + }; + yield PlacesUtils.history.removeVisitsByFilter(filter); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("URI should no longer exist in moz_places."); + do_check_false(page_in_database(TEST_URI.spec)); + + do_print("Run a history query and check that no visits exist."); + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + opts.resultType = opts.RESULTS_AS_VISIT; + opts.sortingMode = opts.SORT_BY_DATE_DESCENDING; + let root = PlacesUtils.history.executeQuery(query, opts).root; + root.containerOpen = true; + do_check_eq(root.childCount, 0); + root.containerOpen = false; + + do_print("asyncHistory.isURIVisited should return false."); + do_check_false(yield promiseIsURIVisited(TEST_URI)); + + yield cleanup(); +}); + +add_task(function* remove_all_visits_bookmarked_uri() { + do_print("*** TEST: Remove all visits from a bookmarked URI"); + + do_print("Add some visits for the URI."); + let visits = []; + for (let i = 0; i < 10; i++) { + visits.push({ uri: TEST_URI, visitDate: DB_NOW - (i * 1000) }); + } + yield PlacesTestUtils.addVisits(visits); + do_print("Bookmark the URI."); + PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, + TEST_URI, + PlacesUtils.bookmarks.DEFAULT_INDEX, + "bookmark title"); + yield PlacesTestUtils.promiseAsyncUpdates(); + let initialFrecency = frecencyForUrl(TEST_URI); + + do_print("Remove all visits."); + let filter = { + beginDate: new Date(JS_NOW - 10), + endDate: new Date(JS_NOW) + }; + yield PlacesUtils.history.removeVisitsByFilter(filter); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("URI should still exist in moz_places."); + do_check_true(page_in_database(TEST_URI.spec)); + + do_print("Run a history query and check that no visits exist."); + let query = PlacesUtils.history.getNewQuery(); + let opts = PlacesUtils.history.getNewQueryOptions(); + opts.resultType = opts.RESULTS_AS_VISIT; + opts.sortingMode = opts.SORT_BY_DATE_DESCENDING; + let root = PlacesUtils.history.executeQuery(query, opts).root; + root.containerOpen = true; + do_check_eq(root.childCount, 0); + root.containerOpen = false; + + do_print("asyncHistory.isURIVisited should return false."); + do_check_false(yield promiseIsURIVisited(TEST_URI)); + + do_print("nsINavBookmarksService.isBookmarked should return true."); + do_check_true(PlacesUtils.bookmarks.isBookmarked(TEST_URI)); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("Frecency should be smaller.") + do_check_true(frecencyForUrl(TEST_URI) < initialFrecency); + + yield cleanup(); +}); + +add_task(function* remove_all_visits_bookmarked_uri() { + do_print("*** TEST: Remove some visits from a zero frecency URI retains zero frecency"); + + do_print("Add some visits for the URI."); + yield PlacesTestUtils.addVisits([ + { uri: TEST_URI, transition: TRANSITION_FRAMED_LINK, visitDate: (DB_NOW - 86400000000000) }, + { uri: TEST_URI, transition: TRANSITION_FRAMED_LINK, visitDate: DB_NOW } + ]); + + do_print("Remove newer visit."); + let filter = { + beginDate: new Date(JS_NOW - 10), + endDate: new Date(JS_NOW) + }; + yield PlacesUtils.history.removeVisitsByFilter(filter); + yield PlacesTestUtils.promiseAsyncUpdates(); + + do_print("URI should still exist in moz_places."); + do_check_true(page_in_database(TEST_URI.spec)); + do_print("Frecency should be zero.") + do_check_eq(frecencyForUrl(TEST_URI), 0); + + yield cleanup(); +}); 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"); +}); diff --git a/toolkit/components/places/tests/history/test_updatePlaces_sameUri_titleChanged.js b/toolkit/components/places/tests/history/test_updatePlaces_sameUri_titleChanged.js new file mode 100644 index 000000000..832df9d9a --- /dev/null +++ b/toolkit/components/places/tests/history/test_updatePlaces_sameUri_titleChanged.js @@ -0,0 +1,52 @@ +// Test that repeated additions of the same URI through updatePlaces, properly +// update from_visit and notify titleChanged. + +add_task(function* test() { + let uri = "http://test.com/"; + + let promiseTitleChangedNotifications = new Promise(resolve => { + let historyObserver = { + _count: 0, + __proto__: NavHistoryObserver.prototype, + onTitleChanged(aURI, aTitle, aGUID) { + Assert.equal(aURI.spec, uri, "Should notify the proper url"); + if (++this._count == 2) { + PlacesUtils.history.removeObserver(historyObserver); + resolve(); + } + } + }; + PlacesUtils.history.addObserver(historyObserver, false); + }); + + // This repeats the url on purpose, don't merge it into a single place entry. + yield PlacesTestUtils.addVisits([ + { uri, title: "test" }, + { uri, referrer: uri, title: "test2" }, + ]); + + let options = PlacesUtils.history.getNewQueryOptions(); + let query = PlacesUtils.history.getNewQuery(); + query.uri = NetUtil.newURI(uri); + options.resultType = options.RESULTS_AS_VISIT; + let root = PlacesUtils.history.executeQuery(query, options).root; + root.containerOpen = true; + + Assert.equal(root.childCount, 2); + + let child = root.getChild(0); + Assert.equal(child.visitType, TRANSITION_LINK, "Visit type should be TRANSITION_LINK"); + Assert.equal(child.visitId, 1, "Visit ID should be 1"); + Assert.equal(child.fromVisitId, -1, "Should have no referrer visit ID"); + Assert.equal(child.title, "test2", "Should have the correct title"); + + child = root.getChild(1); + Assert.equal(child.visitType, TRANSITION_LINK, "Visit type should be TRANSITION_LINK"); + Assert.equal(child.visitId, 2, "Visit ID should be 2"); + Assert.equal(child.fromVisitId, 1, "First visit should be the referring visit"); + Assert.equal(child.title, "test2", "Should have the correct title"); + + root.containerOpen = false; + + yield promiseTitleChangedNotifications; +}); diff --git a/toolkit/components/places/tests/history/xpcshell.ini b/toolkit/components/places/tests/history/xpcshell.ini new file mode 100644 index 000000000..ee182e090 --- /dev/null +++ b/toolkit/components/places/tests/history/xpcshell.ini @@ -0,0 +1,9 @@ +[DEFAULT] +head = head_history.js +tail = + +[test_insert.js] +[test_remove.js] +[test_removeVisits.js] +[test_removeVisitsByFilter.js] +[test_updatePlaces_sameUri_titleChanged.js] |