summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js')
-rw-r--r--toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js465
1 files changed, 465 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js b/toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js
new file mode 100644
index 0000000000..01bb591e3c
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_remove.js
@@ -0,0 +1,465 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const UNVISITED_BOOKMARK_BONUS = 140;
+
+function promiseRankingChanged() {
+ return PlacesTestUtils.waitForNotification("pages-rank-changed");
+}
+
+add_task(async function setup() {
+ Services.prefs.setIntPref(
+ "places.frecency.unvisitedBookmarkBonus",
+ UNVISITED_BOOKMARK_BONUS
+ );
+});
+
+add_task(async function invalid_input_throws() {
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove(),
+ /Input should be a valid object/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove(null),
+ /Input should be a valid object/
+ );
+
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove("test"),
+ /Invalid value for property 'guid'/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove(123),
+ /Invalid value for property 'guid'/
+ );
+
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ guid: "test" }),
+ /Invalid value for property 'guid'/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ guid: null }),
+ /Invalid value for property 'guid'/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ guid: 123 }),
+ /Invalid value for property 'guid'/
+ );
+
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ parentGuid: "test" }),
+ /Invalid value for property 'parentGuid'/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ parentGuid: null }),
+ /Invalid value for property 'parentGuid'/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ parentGuid: 123 }),
+ /Invalid value for property 'parentGuid'/
+ );
+
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ url: "http://te st/" }),
+ /Invalid value for property 'url'/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ url: null }),
+ /Invalid value for property 'url'/
+ );
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove({ url: -10 }),
+ /Invalid value for property 'url'/
+ );
+});
+
+add_task(async function remove_nonexistent_guid() {
+ try {
+ await PlacesUtils.bookmarks.remove({ guid: "123456789012" });
+ Assert.ok(false, "Should have thrown");
+ } catch (ex) {
+ Assert.ok(/No bookmarks found for the provided GUID/.test(ex));
+ }
+});
+
+add_task(async function remove_roots_fail() {
+ let guids = [
+ PlacesUtils.bookmarks.rootGuid,
+ PlacesUtils.bookmarks.unfiledGuid,
+ PlacesUtils.bookmarks.menuGuid,
+ PlacesUtils.bookmarks.toolbarGuid,
+ PlacesUtils.bookmarks.tagsGuid,
+ PlacesUtils.bookmarks.mobileGuid,
+ ];
+ for (let guid of guids) {
+ Assert.throws(
+ () => PlacesUtils.bookmarks.remove(guid),
+ /It's not possible to remove Places root folders\./
+ );
+ }
+});
+
+add_task(async function remove_bookmark() {
+ // When removing a bookmark we need to check the frecency. First we confirm
+ // that there is a normal update when it is inserted.
+ let promise = promiseRankingChanged();
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark",
+ });
+ checkBookmarkObject(bm1);
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+ await promise;
+
+ // This second one checks the frecency is changed when we remove the bookmark.
+ promise = promiseRankingChanged();
+
+ await PlacesUtils.bookmarks.remove(bm1.guid);
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+ await promise;
+});
+
+add_task(async function remove_multiple_bookmarks_simple() {
+ // When removing a bookmark we need to check the frecency. First we confirm
+ // that there is a normal update when it is inserted.
+ const promise1 = promiseRankingChanged();
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "a bookmark",
+ });
+ checkBookmarkObject(bm1);
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+ const promise2 = promiseRankingChanged();
+ let bm2 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example1.com/",
+ title: "a bookmark",
+ });
+ checkBookmarkObject(bm2);
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+ await Promise.all([promise1, promise2]);
+
+ // We should get a pages-rank-changed event with the removal of
+ // multiple bookmarks.
+ const promise3 = promiseRankingChanged();
+
+ await PlacesUtils.bookmarks.remove([bm1, bm2]);
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+ await promise3;
+});
+
+add_task(async function remove_multiple_bookmarks_complex() {
+ let bms = [];
+ for (let i = 0; i < 10; i++) {
+ bms.push(
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ url: `http://example.com/${i}`,
+ title: `bookmark ${i}`,
+ })
+ );
+ }
+
+ // Remove bookmarks 2 and 3.
+ let bmsToRemove = bms.slice(2, 4);
+ let notifiedIndexes = [];
+ let notificationPromise = PlacesTestUtils.waitForNotification(
+ "bookmark-removed",
+ events => {
+ for (let event of events) {
+ notifiedIndexes.push({ guid: event.guid, index: event.index });
+ }
+ return notifiedIndexes.length == bmsToRemove.length;
+ }
+ );
+ await PlacesUtils.bookmarks.remove(bmsToRemove);
+ await notificationPromise;
+
+ let indexModifier = 0;
+ for (let i = 0; i < bmsToRemove.length; i++) {
+ Assert.equal(
+ notifiedIndexes[i].guid,
+ bmsToRemove[i].guid,
+ `Should have been notified of the correct guid for item ${i}`
+ );
+ Assert.equal(
+ notifiedIndexes[i].index,
+ bmsToRemove[i].index - indexModifier,
+ `Should have been notified of the correct index for the item ${i}`
+ );
+ indexModifier++;
+ }
+
+ let expectedIndex = 0;
+ for (let bm of [bms[0], bms[1], ...bms.slice(4)]) {
+ const fetched = await PlacesUtils.bookmarks.fetch(bm.guid);
+ Assert.equal(
+ fetched.index,
+ expectedIndex,
+ "Should have the correct index after consecutive item removal"
+ );
+ bm.index = fetched.index;
+ expectedIndex++;
+ }
+
+ // Remove some more including non-consecutive.
+ bmsToRemove = [bms[1], bms[5], bms[6], bms[8]];
+ notifiedIndexes = [];
+ notificationPromise = PlacesTestUtils.waitForNotification(
+ "bookmark-removed",
+ events => {
+ for (let event of events) {
+ notifiedIndexes.push({ guid: event.guid, index: event.index });
+ }
+ return notifiedIndexes.length == bmsToRemove.length;
+ }
+ );
+ await PlacesUtils.bookmarks.remove(bmsToRemove);
+ await notificationPromise;
+
+ indexModifier = 0;
+ for (let i = 0; i < bmsToRemove.length; i++) {
+ Assert.equal(
+ notifiedIndexes[i].guid,
+ bmsToRemove[i].guid,
+ `Should have been notified of the correct guid for item ${i}`
+ );
+ Assert.equal(
+ notifiedIndexes[i].index,
+ bmsToRemove[i].index - indexModifier,
+ `Should have been notified of the correct index for the item ${i}`
+ );
+ indexModifier++;
+ }
+
+ expectedIndex = 0;
+ const expectedRemaining = [bms[0], bms[4], bms[7], bms[9]];
+ for (let bm of expectedRemaining) {
+ const fetched = await PlacesUtils.bookmarks.fetch(bm.guid);
+ Assert.equal(
+ fetched.index,
+ expectedIndex,
+ "Should have the correct index after non-consecutive item removal"
+ );
+ expectedIndex++;
+ }
+
+ // Tidy up
+ await PlacesUtils.bookmarks.remove(expectedRemaining);
+ await PlacesTestUtils.promiseAsyncUpdates();
+});
+
+add_task(async function remove_bookmark_empty_title() {
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "",
+ });
+ checkBookmarkObject(bm1);
+
+ await PlacesUtils.bookmarks.remove(bm1.guid);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
+});
+
+add_task(async function remove_folder() {
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder",
+ });
+ checkBookmarkObject(bm1);
+
+ await PlacesUtils.bookmarks.remove(bm1.guid);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
+
+ // No wait for pages-rank-changed event in this test as the folder doesn't have
+ // any children that would need updating.
+});
+
+add_task(async function test_contents_removed() {
+ let folder1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder",
+ });
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder1.guid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "",
+ });
+
+ let skipDescendantsObserver = expectPlacesObserverNotifications(
+ ["bookmark-removed"],
+ false,
+ true
+ );
+ let receiveAllObserver = expectPlacesObserverNotifications(
+ ["bookmark-removed"],
+ false,
+ false
+ );
+ const promise = promiseRankingChanged();
+ await PlacesUtils.bookmarks.remove(folder1);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder1.guid), null);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+ await promise;
+
+ let expectedNotifications = [
+ {
+ type: "bookmark-removed",
+ guid: folder1.guid,
+ },
+ ];
+
+ // If we're skipping descendents, we'll only be notified of the folder.
+ skipDescendantsObserver.check(expectedNotifications);
+
+ // Note: Items of folders get notified first.
+ expectedNotifications.unshift({
+ type: "bookmark-removed",
+ guid: bm1.guid,
+ });
+ // If we don't skip descendents, we'll be notified of the folder and the
+ // bookmark.
+ receiveAllObserver.check(expectedNotifications);
+});
+
+add_task(async function test_nested_contents_removed() {
+ let folder1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder",
+ });
+ let folder2 = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder1.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder",
+ });
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder2.guid,
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url: "http://example.com/",
+ title: "",
+ });
+
+ const promise = promiseRankingChanged();
+ await PlacesUtils.bookmarks.remove(folder1);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder1.guid), null);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(folder2.guid), null);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
+ await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
+ await promise;
+});
+
+add_task(async function remove_folder_empty_title() {
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "",
+ });
+ checkBookmarkObject(bm1);
+
+ await PlacesUtils.bookmarks.remove(bm1.guid);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
+});
+
+add_task(async function remove_separator() {
+ let bm1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ });
+ checkBookmarkObject(bm1);
+
+ await PlacesUtils.bookmarks.remove(bm1.guid);
+ Assert.strictEqual(await PlacesUtils.bookmarks.fetch(bm1.guid), null);
+});
+
+add_task(async function test_nested_content_fails_when_not_allowed() {
+ let folder1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder",
+ });
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: folder1.guid,
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: "a folder",
+ });
+ await Assert.rejects(
+ PlacesUtils.bookmarks.remove(folder1, {
+ preventRemovalOfNonEmptyFolders: true,
+ }),
+ /Cannot remove a non-empty folder./
+ );
+});
+
+add_task(async function test_remove_bookmark_with_invalid_url() {
+ let folder = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ title: "folder",
+ });
+ let guid = "invalid_____";
+ let folderedGuid = "invalid____2";
+ let url = "invalid-uri";
+ await PlacesUtils.withConnectionWrapper("test_bookmarks_remove", async db => {
+ await db.execute(
+ `
+ INSERT INTO moz_places(url, url_hash, title, rev_host, guid)
+ VALUES (:url, hash(:url), 'Invalid URI', '.', GENERATE_GUID())
+ `,
+ { url }
+ );
+ await db.execute(
+ `INSERT INTO moz_bookmarks (type, fk, parent, position, guid)
+ VALUES (:type,
+ (SELECT id FROM moz_places WHERE url = :url),
+ (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid),
+ (SELECT MAX(position) + 1 FROM moz_bookmarks WHERE parent = (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid)),
+ :guid)
+ `,
+ {
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ guid,
+ }
+ );
+ await db.execute(
+ `INSERT INTO moz_bookmarks (type, fk, parent, position, guid)
+ VALUES (:type,
+ (SELECT id FROM moz_places WHERE url = :url),
+ (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid),
+ (SELECT MAX(position) + 1 FROM moz_bookmarks WHERE parent = (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid)),
+ :guid)
+ `,
+ {
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ url,
+ parentGuid: folder.guid,
+ guid: folderedGuid,
+ }
+ );
+ });
+ await PlacesUtils.bookmarks.remove(guid);
+ Assert.strictEqual(
+ await PlacesUtils.bookmarks.fetch(guid),
+ null,
+ "Should not throw and not find the bookmark"
+ );
+
+ await PlacesUtils.bookmarks.remove(folder);
+ Assert.strictEqual(
+ await PlacesUtils.bookmarks.fetch(folderedGuid),
+ null,
+ "Should not throw and not find the bookmark"
+ );
+});