Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
const { actionTypes: at, actionCreators: ac } = ChromeUtils.importESModule(
"resource://activity-stream/common/Actions.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
PartnerLinkAttribution: "resource:///modules/PartnerLinkAttribution.sys.mjs",
pktApi: "chrome://pocket/content/pktApi.sys.mjs",
PlacesFeed: "resource://activity-stream/lib/PlacesFeed.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
SearchService: "resource://gre/modules/SearchService.sys.mjs",
});
const { PlacesObserver } = PlacesFeed;
const FAKE_BOOKMARK = {
bookmarkGuid: "D3r1sKRobtbW",
bookmarkTitle: "Foo",
dateAdded: 123214232,
url: "foo.com",
};
const TYPE_BOOKMARK = 1; // This is fake, for testing
const SOURCES = {
DEFAULT: 0,
SYNC: 1,
IMPORT: 2,
RESTORE: 5,
RESTORE_ON_STARTUP: 6,
};
// The event dispatched in NewTabUtils when a link is blocked;
const BLOCKED_EVENT = "newtab-linkBlocked";
const TOP_SITES_BLOCKED_SPONSORS_PREF = "browser.topsites.blockedSponsors";
const POCKET_SITE_PREF = "extensions.pocket.site";
function getPlacesFeedForTest(sandbox) {
let feed = new PlacesFeed();
feed.store = {
dispatch: sandbox.spy(),
feeds: {
get: sandbox.stub(),
},
};
sandbox.stub(AboutNewTab, "activityStream").value({
store: feed.store,
});
return feed;
}
add_task(async function test_construction() {
info("PlacesFeed construction should work");
let feed = new PlacesFeed();
Assert.ok(feed, "PlacesFeed could be constructed.");
});
add_task(async function test_PlacesObserver() {
info("PlacesFeed should have a PlacesObserver that dispatches to the store");
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let action = { type: "FOO" };
feed.placesObserver.dispatch(action);
await TestUtils.waitForTick();
Assert.ok(feed.store.dispatch.calledOnce, "PlacesFeed.store dispatch called");
Assert.equal(feed.store.dispatch.firstCall.args[0].type, action.type);
sandbox.restore();
});
add_task(async function test_addToBlockedTopSitesSponsors_add_to_blocklist() {
info(
"PlacesFeed.addToBlockedTopSitesSponsors should add the blocked sponsors " +
"to the blocklist"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
Services.prefs.setStringPref(
TOP_SITES_BLOCKED_SPONSORS_PREF,
`["foo","bar"]`
);
feed.addToBlockedTopSitesSponsors([
{ url: "test.com" },
{ url: "test1.com" },
]);
let blockedSponsors = JSON.parse(
Services.prefs.getStringPref(TOP_SITES_BLOCKED_SPONSORS_PREF)
);
Assert.deepEqual(
new Set(["foo", "bar", "test", "test1"]),
new Set(blockedSponsors)
);
Services.prefs.clearUserPref(TOP_SITES_BLOCKED_SPONSORS_PREF);
sandbox.restore();
});
add_task(async function test_addToBlockedTopSitesSponsors_no_dupes() {
info(
"PlacesFeed.addToBlockedTopSitesSponsors should not add duplicate " +
"sponsors to the blocklist"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
Services.prefs.setStringPref(
TOP_SITES_BLOCKED_SPONSORS_PREF,
`["foo","bar"]`
);
feed.addToBlockedTopSitesSponsors([
{ url: "foo.com" },
{ url: "bar.com" },
{ url: "test.com" },
]);
let blockedSponsors = JSON.parse(
Services.prefs.getStringPref(TOP_SITES_BLOCKED_SPONSORS_PREF)
);
Assert.deepEqual(new Set(["foo", "bar", "test"]), new Set(blockedSponsors));
Services.prefs.clearUserPref(TOP_SITES_BLOCKED_SPONSORS_PREF);
sandbox.restore();
});
add_task(async function test_onAction_PlacesEvents() {
info(
"PlacesFeed.onAction should add bookmark, history, places, blocked " +
"observers on INIT"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed.placesObserver, "handlePlacesEvent");
feed.onAction({ type: at.INIT });
// The PlacesObserver registration happens at the next tick of the
// event loop.
await TestUtils.waitForTick();
// These are some dummy PlacesEvents that we'll pass through the
// PlacesObserver service, checking that the handlePlacesEvent receives them
// properly.
let notifications = [
new PlacesBookmarkAddition({
dateAdded: 0,
guid: "dQFSYrbM5SJN",
id: -1,
index: 0,
isTagging: false,
itemType: 1,
parentGuid: "n_HOEFys1qsL",
parentId: -2,
source: 0,
title: "test-123",
tags: "tags",
frecency: 0,
hidden: false,
visitCount: 0,
lastVisitDate: 0,
targetFolderGuid: null,
targetFolderItemId: -1,
targetFolderTitle: null,
}),
new PlacesBookmarkRemoved({
id: -1,
title: "test-123",
itemType: 1,
parentId: -2,
index: 0,
guid: "M3WYgJlm2Jlx",
parentGuid: "DO1f97R4KC3Y",
source: 0,
isTagging: false,
isDescendantRemoval: false,
}),
new PlacesHistoryCleared(),
new PlacesVisitRemoved({
pageGuid: "sPVcW2V4H7Rg",
reason: PlacesVisitRemoved.REASON_DELETED,
transitionType: 0,
isRemovedFromStore: true,
isPartialVisistsRemoval: false,
}),
];
for (let notification of notifications) {
PlacesUtils.observers.notifyListeners([notification]);
Assert.ok(
feed.placesObserver.handlePlacesEvent.calledOnce,
"PlacesFeed.handlePlacesEvent called"
);
Assert.ok(feed.placesObserver.handlePlacesEvent.calledWith([notification]));
feed.placesObserver.handlePlacesEvent.resetHistory();
}
info(
"PlacesFeed.onAction remove bookmark, history, places, blocked " +
"observers, and timers on UNINIT"
);
let placesChangedTimerCancel = sandbox.spy();
feed.placesChangedTimer = {
cancel: placesChangedTimerCancel,
};
// Unlike INIT, UNINIT removes the observers synchronously, so no need to
// wait for the event loop to tick around again.
feed.onAction({ type: at.UNINIT });
for (let notification of notifications) {
PlacesUtils.observers.notifyListeners([notification]);
Assert.ok(
feed.placesObserver.handlePlacesEvent.notCalled,
"PlacesFeed.handlePlacesEvent not called"
);
feed.placesObserver.handlePlacesEvent.resetHistory();
}
Assert.equal(feed.placesChangedTimer, null);
Assert.ok(placesChangedTimerCancel.calledOnce);
sandbox.restore();
});
add_task(async function test_onAction_BLOCK_URL() {
info("PlacesFeed.onAction should block a url on BLOCK_URL");
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(NewTabUtils.activityStreamLinks, "blockURL");
feed.onAction({
type: at.BLOCK_URL,
data: [{ url: "apple.com", pocket_id: 1234 }],
});
Assert.ok(
NewTabUtils.activityStreamLinks.blockURL.calledWith({
url: "apple.com",
pocket_id: 1234,
})
);
sandbox.restore();
});
add_task(async function test_onAction_BLOCK_URL_topsites_sponsors() {
info(
"PlacesFeed.onAction BLOCK_URL should update the blocked top " +
"sites sponsors"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "addToBlockedTopSitesSponsors");
feed.onAction({
type: at.BLOCK_URL,
data: [{ url: "foo.com", pocket_id: 1234, isSponsoredTopSite: 1 }],
});
Assert.ok(feed.addToBlockedTopSitesSponsors.calledWith([{ url: "foo.com" }]));
sandbox.restore();
});
add_task(async function test_onAction_BOOKMARK_URL() {
info("PlacesFeed.onAction should bookmark a url on BOOKMARK_URL");
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(NewTabUtils.activityStreamLinks, "addBookmark");
let data = { url: "pear.com", title: "A pear" };
let _target = { browser: { ownerGlobal() {} } };
feed.onAction({ type: at.BOOKMARK_URL, data, _target });
Assert.ok(
NewTabUtils.activityStreamLinks.addBookmark.calledWith(
data,
_target.browser.ownerGlobal
)
);
sandbox.restore();
});
add_task(async function test_onAction_DELETE_BOOKMARK_BY_ID() {
info("PlacesFeed.onAction should delete a bookmark on DELETE_BOOKMARK_BY_ID");
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(NewTabUtils.activityStreamLinks, "deleteBookmark");
feed.onAction({ type: at.DELETE_BOOKMARK_BY_ID, data: "g123kd" });
Assert.ok(
NewTabUtils.activityStreamLinks.deleteBookmark.calledWith("g123kd")
);
sandbox.restore();
});
add_task(async function test_onAction_DELETE_HISTORY_URL() {
info(
"PlacesFeed.onAction should delete a history entry on DELETE_HISTORY_URL"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(NewTabUtils.activityStreamLinks, "deleteHistoryEntry");
sandbox.stub(NewTabUtils.activityStreamLinks, "blockURL");
feed.onAction({
type: at.DELETE_HISTORY_URL,
data: { url: "guava.com", forceBlock: null },
});
Assert.ok(
NewTabUtils.activityStreamLinks.deleteHistoryEntry.calledWith("guava.com")
);
Assert.ok(NewTabUtils.activityStreamLinks.blockURL.notCalled);
sandbox.restore();
});
add_task(async function test_onAction_DELETE_HISTORY_URL_and_block() {
info(
"PlacesFeed.onAction should delete a history entry on " +
"DELETE_HISTORY_URL and force a site to be blocked if specified"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(NewTabUtils.activityStreamLinks, "deleteHistoryEntry");
sandbox.stub(NewTabUtils.activityStreamLinks, "blockURL");
feed.onAction({
type: at.DELETE_HISTORY_URL,
data: { url: "guava.com", forceBlock: "g123kd" },
});
Assert.ok(
NewTabUtils.activityStreamLinks.deleteHistoryEntry.calledWith("guava.com")
);
Assert.ok(
NewTabUtils.activityStreamLinks.blockURL.calledWith({
url: "guava.com",
pocket_id: undefined,
})
);
sandbox.restore();
});
add_task(async function test_onAction_OPEN_NEW_WINDOW() {
info(
"PlacesFeed.onAction should call openTrustedLinkIn with the " +
"correct url, where and params on OPEN_NEW_WINDOW"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let openWindowAction = {
type: at.OPEN_NEW_WINDOW,
data: { url: "https://foo.com" },
_target: { browser: { ownerGlobal: { openTrustedLinkIn } } },
};
feed.onAction(openWindowAction);
Assert.ok(openTrustedLinkIn.calledOnce, "openTrustedLinkIn called");
let [url, where, params] = openTrustedLinkIn.firstCall.args;
Assert.equal(url, "https://foo.com");
Assert.equal(where, "window");
Assert.ok(!params.private);
Assert.ok(!params.forceForeground);
sandbox.restore();
});
add_task(async function test_onAction_OPEN_PRIVATE_WINDOW() {
info(
"PlacesFeed.onAction should call openTrustedLinkIn with the " +
"correct url, where, params and privacy args on OPEN_PRIVATE_WINDOW"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let openWindowAction = {
type: at.OPEN_PRIVATE_WINDOW,
data: { url: "https://foo.com" },
_target: { browser: { ownerGlobal: { openTrustedLinkIn } } },
};
feed.onAction(openWindowAction);
Assert.ok(openTrustedLinkIn.calledOnce, "openTrustedLinkIn called");
let [url, where, params] = openTrustedLinkIn.firstCall.args;
Assert.equal(url, "https://foo.com");
Assert.equal(where, "window");
Assert.ok(params.private);
Assert.ok(!params.forceForeground);
sandbox.restore();
});
add_task(async function test_onAction_OPEN_LINK() {
info(
"PlacesFeed.onAction should call openTrustedLinkIn with the " +
"correct url, where and params on OPEN_LINK"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let openLinkAction = {
type: at.OPEN_LINK,
data: { url: "https://foo.com" },
_target: {
browser: {
ownerGlobal: { openTrustedLinkIn },
},
},
};
feed.onAction(openLinkAction);
Assert.ok(openTrustedLinkIn.calledOnce, "openTrustedLinkIn called");
let [url, where, params] = openTrustedLinkIn.firstCall.args;
Assert.equal(url, "https://foo.com");
Assert.equal(where, "current");
Assert.ok(!params.private);
Assert.ok(!params.forceForeground);
sandbox.restore();
});
add_task(async function test_onAction_OPEN_LINK_referrer() {
info("PlacesFeed.onAction should open link with referrer on OPEN_LINK");
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let openLinkAction = {
type: at.OPEN_LINK,
data: { url: "https://foo.com", referrer: "https://foo.com/ref" },
_target: {
browser: {
ownerGlobal: { openTrustedLinkIn, whereToOpenLink: () => "tab" },
},
},
};
feed.onAction(openLinkAction);
Assert.ok(openTrustedLinkIn.calledOnce, "openTrustedLinkIn called");
let [, , params] = openTrustedLinkIn.firstCall.args;
Assert.equal(params.referrerInfo.referrerPolicy, 5);
Assert.equal(
params.referrerInfo.originalReferrer.spec,
);
sandbox.restore();
});
add_task(async function test_onAction_OPEN_LINK_typed_bonus() {
info(
"PlacesFeed.onAction should mark link with typed bonus as " +
"typed before opening OPEN_LINK"
);
let sandbox = sinon.createSandbox();
let callOrder = [];
// We can't stub out PlacesUtils.history.markPageAsTyped, since that's an
// XPCOM component. We'll stub out history instead.
sandbox.stub(PlacesUtils, "history").get(() => {
return {
markPageAsTyped: sandbox.stub().callsFake(() => {
callOrder.push("markPageAsTyped");
}),
};
});
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub().callsFake(() => {
callOrder.push("openTrustedLinkIn");
});
let openLinkAction = {
type: at.OPEN_LINK,
data: {
typedBonus: true,
url: "https://foo.com",
},
_target: {
browser: {
ownerGlobal: { openTrustedLinkIn, whereToOpenLink: () => "tab" },
},
},
};
feed.onAction(openLinkAction);
Assert.deepEqual(callOrder, ["markPageAsTyped", "openTrustedLinkIn"]);
sandbox.restore();
});
add_task(async function test_onAction_OPEN_LINK_pocket() {
info(
"PlacesFeed.onAction should open the pocket link if it's a " +
"pocket story on OPEN_LINK"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let openLinkAction = {
type: at.OPEN_LINK,
data: {
url: "https://foo.com",
type: "pocket",
},
_target: {
browser: {
ownerGlobal: { openTrustedLinkIn },
},
},
};
feed.onAction(openLinkAction);
Assert.ok(openTrustedLinkIn.calledOnce, "openTrustedLinkIn called");
let [url, where, params] = openTrustedLinkIn.firstCall.args;
Assert.equal(url, "https://getpocket.com/foo");
Assert.equal(where, "current");
Assert.ok(!params.private);
Assert.ok(!params.forceForeground);
sandbox.restore();
});
add_task(async function test_onAction_OPEN_LINK_not_http() {
info("PlacesFeed.onAction should not open link if not http");
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let openLinkAction = {
type: at.OPEN_LINK,
data: { url: "file:///foo.com" },
_target: {
browser: {
ownerGlobal: { openTrustedLinkIn },
},
},
};
feed.onAction(openLinkAction);
Assert.ok(openTrustedLinkIn.notCalled, "openTrustedLinkIn not called");
sandbox.restore();
});
add_task(async function test_onAction_FILL_SEARCH_TERM() {
info(
"PlacesFeed.onAction should call fillSearchTopSiteTerm " +
"on FILL_SEARCH_TERM"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "fillSearchTopSiteTerm");
feed.onAction({ type: at.FILL_SEARCH_TERM });
Assert.ok(
feed.fillSearchTopSiteTerm.calledOnce,
"PlacesFeed.fillSearchTopSiteTerm called"
);
sandbox.restore();
});
add_task(async function test_onAction_ABOUT_SPONSORED_TOP_SITES() {
info(
"PlacesFeed.onAction should call openTrustedLinkIn with the " +
"correct SUMO url on ABOUT_SPONSORED_TOP_SITES"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let openLinkAction = {
type: at.ABOUT_SPONSORED_TOP_SITES,
_target: {
browser: {
ownerGlobal: { openTrustedLinkIn },
},
},
};
feed.onAction(openLinkAction);
Assert.ok(openTrustedLinkIn.calledOnce, "openTrustedLinkIn called");
let [url, where] = openTrustedLinkIn.firstCall.args;
Assert.ok(url.endsWith("sponsor-privacy"));
Assert.equal(where, "tab");
sandbox.restore();
});
add_task(async function test_onAction_FILL_SEARCH_TERM() {
info(
"PlacesFeed.onAction should set the URL bar value to the label value " +
"on FILL_SEARCH_TERM"
);
let sandbox = sinon.createSandbox();
sandbox.stub(SearchService.prototype, "getEngineByAlias").resolves(null);
let feed = getPlacesFeedForTest(sandbox);
let locationBar = { search: sandbox.stub() };
let action = {
type: at.FILL_SEARCH_TERM,
data: { label: "@Foo" },
_target: { browser: { ownerGlobal: { gURLBar: locationBar } } },
};
await feed.onAction(action);
Assert.ok(locationBar.search.calledOnce, "gURLBar.search called");
Assert.ok(
locationBar.search.calledWithExactly("@Foo", {
searchEngine: null,
searchModeEntry: "topsites_newtab",
})
);
sandbox.restore();
});
add_task(async function test_onAction_SAVE_TO_POCKET() {
info("PlacesFeed.onAction should call saveToPocket on SAVE_TO_POCKET");
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "saveToPocket");
let action = {
type: at.SAVE_TO_POCKET,
data: { site: { url: "raspberry.com", title: "raspberry" } },
_target: { browser: {} },
};
await feed.onAction(action);
Assert.ok(feed.saveToPocket.calledOnce, "PlacesFeed.saveToPocket called");
Assert.ok(
feed.saveToPocket.calledWithExactly(
action.data.site,
action._target.browser
)
);
sandbox.restore();
});
add_task(async function test_onAction_SAVE_TO_POCKET_not_logged_in() {
info(
"PlacesFeed.onAction should openTrustedLinkIn with sendToPocket " +
"if not logged in on SAVE_TO_POCKET"
);
let sandbox = sinon.createSandbox();
sandbox.stub(pktApi, "isUserLoggedIn").returns(false);
sandbox.stub(NimbusFeatures.pocketNewtab, "getVariable").returns(true);
sandbox.stub(ExperimentAPI, "getExperiment").returns({
slug: "slug",
branch: { slug: "branch-slug" },
});
Services.prefs.setStringPref(POCKET_SITE_PREF, "getpocket.com");
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let action = {
type: at.SAVE_TO_POCKET,
data: { site: { url: "raspberry.com", title: "raspberry" } },
_target: {
browser: {
ownerGlobal: {
openTrustedLinkIn,
},
},
},
};
await feed.onAction(action);
Assert.ok(openTrustedLinkIn.calledOnce, "openTrustedLinkIn called");
let [url, where] = openTrustedLinkIn.firstCall.args;
Assert.equal(
url,
);
Assert.equal(where, "tab");
Services.prefs.clearUserPref(POCKET_SITE_PREF);
sandbox.restore();
});
add_task(async function test_onAction_SAVE_TO_POCKET_logged_in() {
info(
"PlacesFeed.onAction should call " +
"NewTabUtils.activityStreamLinks.addPocketEntry if we are saving a " +
"pocket story on SAVE_TO_POCKET"
);
let sandbox = sinon.createSandbox();
sandbox.stub(pktApi, "isUserLoggedIn").returns(true);
sandbox.stub(NewTabUtils.activityStreamLinks, "addPocketEntry");
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let action = {
type: at.SAVE_TO_POCKET,
data: { site: { url: "raspberry.com", title: "raspberry" } },
_target: {
browser: {
ownerGlobal: {
openTrustedLinkIn,
},
},
},
};
await feed.onAction(action);
Assert.ok(
NewTabUtils.activityStreamLinks.addPocketEntry.calledOnce,
"NewTabUtils.activityStreamLinks.addPocketEntry called"
);
Assert.ok(
NewTabUtils.activityStreamLinks.addPocketEntry.calledWithExactly(
action.data.site.url,
action.data.site.title,
action._target.browser
)
);
sandbox.restore();
});
add_task(async function test_saveToPocket_addPocketEntry_rejects() {
info(
"PlacesFeed.saveToPocket should still resolve if " +
"NewTabUtils.activityStreamLinks.addPocketEntry rejects"
);
let sandbox = sinon.createSandbox();
let e = new Error("Error");
sandbox.stub(NewTabUtils.activityStreamLinks, "addPocketEntry").rejects(e);
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let action = {
data: { site: { url: "raspberry.com", title: "raspberry" } },
_target: {
browser: {
ownerGlobal: {
openTrustedLinkIn,
},
},
},
};
try {
await feed.saveToPocket(action.data.site, action._target.browser);
Assert.ok(true, "PlacesFeed.saveToPocket Promise resolved");
} catch {
Assert.ok(false, "PlacesFeed.saveToPocket Promise rejected");
}
sandbox.restore();
});
add_task(async function test_saveToPocket_broadcast_to_content() {
info(
"PlacesFeed.saveToPocket should broadcast to content if we " +
"successfully added a link to Pocket"
);
let sandbox = sinon.createSandbox();
sandbox.stub(pktApi, "isUserLoggedIn").returns(true);
sandbox
.stub(NewTabUtils.activityStreamLinks, "addPocketEntry")
.resolves({ item: { open_url: "pocket.com/itemID", item_id: 1234 } });
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let action = {
data: { site: { url: "raspberry.com", title: "raspberry" } },
_target: {
browser: {
ownerGlobal: {
openTrustedLinkIn,
},
},
},
};
await feed.saveToPocket(action.data.site, action._target.browser);
Assert.ok(
feed.store.dispatch.calledOnce,
"PlacesFeed.store.dispatch was called"
);
Assert.equal(
feed.store.dispatch.firstCall.args[0].type,
at.PLACES_SAVED_TO_POCKET
);
Assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, {
url: "raspberry.com",
title: "raspberry",
pocket_id: 1234,
open_url: "pocket.com/itemID",
});
sandbox.restore();
});
add_task(async function test_saveToPocket_broadcast_only_on_data() {
info(
"PlacesFeed.saveToPocket should broadcast to content if we " +
"successfully added a link to Pocket"
);
let sandbox = sinon.createSandbox();
sandbox.stub(pktApi, "isUserLoggedIn").returns(true);
sandbox
.stub(NewTabUtils.activityStreamLinks, "addPocketEntry")
.resolves(null);
let feed = getPlacesFeedForTest(sandbox);
let openTrustedLinkIn = sandbox.stub();
let action = {
data: { site: { url: "raspberry.com", title: "raspberry" } },
_target: {
browser: {
ownerGlobal: {
openTrustedLinkIn,
},
},
},
};
await feed.saveToPocket(action.data.site, action._target.browser);
Assert.ok(
feed.store.dispatch.notCalled,
"PlacesFeed.store.dispatch was not called"
);
sandbox.restore();
});
add_task(async function test_onAction_DELETE_FROM_POCKET() {
info(
"PlacesFeed.onAction should call deleteFromPocket on DELETE_FROM_POCKET"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "deleteFromPocket");
feed.onAction({
type: at.DELETE_FROM_POCKET,
data: { pocket_id: 12345 },
});
Assert.ok(
feed.deleteFromPocket.calledOnce,
"PlacesFeed.deleteFromPocket called"
);
Assert.ok(feed.deleteFromPocket.calledWithExactly(12345));
sandbox.restore();
});
add_task(async function test_deleteFromPocket_resolves() {
info(
"PlacesFeed.deleteFromPocket should still resolve if deletePocketEntry " +
"rejects"
);
let sandbox = sinon.createSandbox();
let e = new Error("Error");
sandbox.stub(NewTabUtils.activityStreamLinks, "deletePocketEntry").rejects(e);
let feed = getPlacesFeedForTest(sandbox);
await feed.deleteFromPocket(12345);
try {
await feed.deleteFromPocket(12345);
Assert.ok(true, "PlacesFeed.deleteFromPocket Promise resolved");
} catch {
Assert.ok(false, "PlacesFeed.deleteFromPocket Promise rejected");
}
sandbox.restore();
});
add_task(async function test_deleteFromPocket_calls_deletePocketEntry() {
info(
"PlacesFeed.deleteFromPocket should call " +
"NewTabUtils.deletePocketEntry and dispatch " +
"POCKET_LINK_DELETED_OR_ARCHIVED when deleting from Pocket"
);
let sandbox = sinon.createSandbox();
sandbox.stub(NewTabUtils.activityStreamLinks, "deletePocketEntry");
let feed = getPlacesFeedForTest(sandbox);
await feed.deleteFromPocket(12345);
Assert.ok(
NewTabUtils.activityStreamLinks.deletePocketEntry.calledOnce,
"NewTabUtils.activityStreamLinks.deletePocketEntry called"
);
Assert.ok(
NewTabUtils.activityStreamLinks.deletePocketEntry.calledWithExactly(12345)
);
Assert.ok(
feed.store.dispatch.calledOnce,
"PlacesFeed.store.dispatch was called"
);
Assert.ok(
feed.store.dispatch.calledWithExactly({
type: at.POCKET_LINK_DELETED_OR_ARCHIVED,
})
);
sandbox.restore();
});
add_task(async function test_onAction_ARCHIVE_FROM_POCKET() {
info(
"PlacesFeed.onAction should call archiveFromPocket on ARCHIVE_FROM_POCKET"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "archiveFromPocket");
await feed.onAction({
type: at.ARCHIVE_FROM_POCKET,
data: { pocket_id: 12345 },
});
Assert.ok(
feed.archiveFromPocket.calledOnce,
"PlacesFeed.archiveFromPocket called"
);
Assert.ok(feed.archiveFromPocket.calledWithExactly(12345));
sandbox.restore();
});
add_task(async function test_archiveFromPocket_resolves() {
info(
"PlacesFeed.archiveFromPocket should resolve if archivePocketEntry rejects"
);
let sandbox = sinon.createSandbox();
let e = new Error("Error");
sandbox
.stub(NewTabUtils.activityStreamLinks, "archivePocketEntry")
.rejects(e);
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "archiveFromPocket");
try {
await feed.archiveFromPocket(12345);
Assert.ok(true, "PlacesFeed.archiveFromPocket Promise resolved");
} catch {
Assert.ok(false, "PlacesFeed.archiveFromPocket Promise rejected");
}
sandbox.restore();
});
add_task(async function test_archiveFromPocket_calls_archivePocketEntry() {
info(
"PlacesFeed.archiveFromPocket should call " +
"NewTabUtils.archivePocketEntry and dispatch " +
"POCKET_LINK_DELETED_OR_ARCHIVED when deleting from Pocket"
);
let sandbox = sinon.createSandbox();
sandbox.stub(NewTabUtils.activityStreamLinks, "archivePocketEntry");
let feed = getPlacesFeedForTest(sandbox);
await feed.archiveFromPocket(12345);
Assert.ok(
NewTabUtils.activityStreamLinks.archivePocketEntry.calledOnce,
"NewTabUtils.activityStreamLinks.archivePocketEntry called"
);
Assert.ok(
NewTabUtils.activityStreamLinks.archivePocketEntry.calledWithExactly(12345)
);
Assert.ok(
feed.store.dispatch.calledOnce,
"PlacesFeed.store.dispatch was called"
);
Assert.ok(
feed.store.dispatch.calledWithExactly({
type: at.POCKET_LINK_DELETED_OR_ARCHIVED,
})
);
sandbox.restore();
});
add_task(async function test_onAction_HANDOFF_SEARCH_TO_AWESOMEBAR() {
info(
"PlacesFeed.onAction should call handoffSearchToAwesomebar " +
"on HANDOFF_SEARCH_TO_AWESOMEBAR"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "handoffSearchToAwesomebar");
let action = {
type: at.HANDOFF_SEARCH_TO_AWESOMEBAR,
data: { text: "f" },
meta: { fromTarget: {} },
_target: { browser: { ownerGlobal: { gURLBar: { focus: () => {} } } } },
};
await feed.onAction(action);
Assert.ok(
feed.handoffSearchToAwesomebar.calledOnce,
"PlacesFeed.handoffSearchToAwesomebar called"
);
Assert.ok(feed.handoffSearchToAwesomebar.calledWithExactly(action));
sandbox.restore();
});
add_task(async function test_onAction_PARTNER_LINK_ATTRIBUTION() {
info(
"PlacesFeed.onAction should call makeAttributionRequest on " +
"PARTNER_LINK_ATTRIBUTION"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(feed, "makeAttributionRequest");
let data = { targetURL: "https://partnersite.com", source: "topsites" };
feed.onAction({
type: at.PARTNER_LINK_ATTRIBUTION,
data,
});
Assert.ok(
feed.makeAttributionRequest.calledOnce,
"PlacesFeed.makeAttributionRequest called"
);
Assert.ok(feed.makeAttributionRequest.calledWithExactly(data));
sandbox.restore();
});
add_task(
async function test_makeAttributionRequest_PartnerLinkAttribution_makeReq() {
info(
"PlacesFeed.makeAttributionRequest should call " +
"PartnerLinkAttribution.makeRequest"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
sandbox.stub(PartnerLinkAttribution, "makeRequest");
let data = { targetURL: "https://partnersite.com", source: "topsites" };
feed.makeAttributionRequest(data);
Assert.ok(
PartnerLinkAttribution.makeRequest.calledOnce,
"PartnerLinkAttribution.makeRequest called"
);
sandbox.restore();
}
);
function createFakeURLBar(sandbox) {
let fakeURLBar = {
focus: sandbox.spy(),
handoff: sandbox.spy(),
setHiddenFocus: sandbox.spy(),
removeHiddenFocus: sandbox.spy(),
addEventListener: (ev, cb) => {
fakeURLBar.listeners[ev] = cb;
},
removeEventListener: sandbox.spy(),
listeners: [],
};
return fakeURLBar;
}
add_task(async function test_handoffSearchToAwesomebar_no_text() {
info(
"PlacesFeed.handoffSearchToAwesomebar should properly handle handoff " +
"with no text passed in"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let fakeURLBar = createFakeURLBar(sandbox);
sandbox.stub(PrivateBrowsingUtils, "isBrowserPrivate").returns(false);
sandbox.stub(feed, "_getDefaultSearchEngine").returns(null);
feed.handoffSearchToAwesomebar({
_target: { browser: { ownerGlobal: { gURLBar: fakeURLBar } } },
data: {},
meta: { fromTarget: {} },
});
Assert.ok(
fakeURLBar.setHiddenFocus.calledOnce,
"gURLBar.setHiddenFocus called"
);
Assert.ok(fakeURLBar.handoff.notCalled, "gURLBar.handoff not called");
Assert.ok(
feed.store.dispatch.notCalled,
"PlacesFeed.store.dispatch not called"
);
// Now type a character.
fakeURLBar.listeners.keydown({ key: "f" });
Assert.ok(fakeURLBar.handoff.calledOnce, "gURLBar.handoff called");
Assert.ok(
fakeURLBar.removeHiddenFocus.calledOnce,
"gURLBar.removeHiddenFocus called"
);
Assert.ok(feed.store.dispatch.calledOnce, "PlacesFeed.store.dispatch called");
Assert.ok(
feed.store.dispatch.calledWith({
meta: {
from: "ActivityStream:Main",
skipMain: true,
to: "ActivityStream:Content",
toTarget: {},
},
type: "DISABLE_SEARCH",
}),
"PlacesFeed.store.dispatch called"
);
sandbox.restore();
});
add_task(async function test_handoffSearchToAwesomebar_with_text() {
info(
"PlacesFeed.handoffSearchToAwesomebar should properly handle handoff " +
"with text data passed in"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let fakeURLBar = createFakeURLBar(sandbox);
sandbox.stub(PrivateBrowsingUtils, "isBrowserPrivate").returns(false);
let engine = {};
sandbox.stub(feed, "_getDefaultSearchEngine").returns(engine);
const SESSION_ID = "decafc0ffee";
AboutNewTab.activityStream.store.feeds.get.returns({
sessions: {
get: () => {
return { session_id: SESSION_ID };
},
},
});
feed.handoffSearchToAwesomebar({
_target: { browser: { ownerGlobal: { gURLBar: fakeURLBar } } },
data: { text: "foo" },
meta: { fromTarget: {} },
});
Assert.ok(fakeURLBar.handoff.calledOnce, "gURLBar.handoff was called");
Assert.ok(fakeURLBar.handoff.calledWithExactly("foo", engine, SESSION_ID));
Assert.ok(fakeURLBar.focus.notCalled, "gURLBar.focus not called");
Assert.ok(
fakeURLBar.setHiddenFocus.notCalled,
"gURLBar.setHiddenFocus not called"
);
// Now call blur listener.
fakeURLBar.listeners.blur();
Assert.ok(feed.store.dispatch.calledOnce, "PlacesFeed.store.dispatch called");
Assert.ok(
feed.store.dispatch.calledWith({
meta: {
from: "ActivityStream:Main",
skipMain: true,
to: "ActivityStream:Content",
toTarget: {},
},
type: "SHOW_SEARCH",
})
);
sandbox.restore();
});
add_task(async function test_handoffSearchToAwesomebar_with_text_pb_mode() {
info(
"PlacesFeed.handoffSearchToAwesomebar should properly handle handoff " +
"with text data passed in, in private browsing mode"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let fakeURLBar = createFakeURLBar(sandbox);
sandbox.stub(PrivateBrowsingUtils, "isBrowserPrivate").returns(true);
let engine = {};
sandbox.stub(feed, "_getDefaultSearchEngine").returns(engine);
feed.handoffSearchToAwesomebar({
_target: { browser: { ownerGlobal: { gURLBar: fakeURLBar } } },
data: { text: "foo" },
meta: { fromTarget: {} },
});
Assert.ok(fakeURLBar.handoff.calledOnce, "gURLBar.handoff was called");
Assert.ok(fakeURLBar.handoff.calledWithExactly("foo", engine, undefined));
Assert.ok(fakeURLBar.focus.notCalled, "gURLBar.focus not called");
Assert.ok(
fakeURLBar.setHiddenFocus.notCalled,
"gURLBar.setHiddenFocus not called"
);
// Now call blur listener.
fakeURLBar.listeners.blur();
Assert.ok(feed.store.dispatch.calledOnce, "PlacesFeed.store.dispatch called");
Assert.ok(
feed.store.dispatch.calledWith({
meta: {
from: "ActivityStream:Main",
skipMain: true,
to: "ActivityStream:Content",
toTarget: {},
},
type: "SHOW_SEARCH",
})
);
sandbox.restore();
});
add_task(async function test_handoffSearchToAwesomebar_SHOW_SEARCH_on_esc() {
info(
"PlacesFeed.handoffSearchToAwesomebar should SHOW_SEARCH on ESC keydown"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let fakeURLBar = createFakeURLBar(sandbox);
sandbox.stub(PrivateBrowsingUtils, "isBrowserPrivate").returns(false);
let engine = {};
sandbox.stub(feed, "_getDefaultSearchEngine").returns(engine);
feed.handoffSearchToAwesomebar({
_target: { browser: { ownerGlobal: { gURLBar: fakeURLBar } } },
data: { text: "foo" },
meta: { fromTarget: {} },
});
Assert.ok(fakeURLBar.handoff.calledOnce, "gURLBar.handoff was called");
Assert.ok(fakeURLBar.handoff.calledWithExactly("foo", engine, undefined));
Assert.ok(fakeURLBar.focus.notCalled, "gURLBar.focus not called");
// Now call ESC keydown.
fakeURLBar.listeners.keydown({ key: "Escape" });
Assert.ok(feed.store.dispatch.calledOnce, "PlacesFeed.store.dispatch called");
Assert.ok(
feed.store.dispatch.calledWith({
meta: {
from: "ActivityStream:Main",
skipMain: true,
to: "ActivityStream:Content",
toTarget: {},
},
type: "SHOW_SEARCH",
})
);
sandbox.restore();
});
add_task(
async function test_handoffSearchToAwesomebar_with_session_id_no_text() {
info(
"PlacesFeed.handoffSearchToAwesomebar should properly handoff a " +
"newtab session id with no text passed in"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
let fakeURLBar = createFakeURLBar(sandbox);
sandbox.stub(PrivateBrowsingUtils, "isBrowserPrivate").returns(false);
let engine = {};
sandbox.stub(feed, "_getDefaultSearchEngine").returns(engine);
const SESSION_ID = "decafc0ffee";
AboutNewTab.activityStream.store.feeds.get.returns({
sessions: {
get: () => {
return { session_id: SESSION_ID };
},
},
});
feed.handoffSearchToAwesomebar({
_target: { browser: { ownerGlobal: { gURLBar: fakeURLBar } } },
data: {},
meta: { fromTarget: {} },
});
Assert.ok(
fakeURLBar.setHiddenFocus.calledOnce,
"gURLBar.setHiddenFocus was called"
);
Assert.ok(fakeURLBar.handoff.notCalled, "gURLBar.handoff not called");
Assert.ok(fakeURLBar.focus.notCalled, "gURLBar.focus not called");
Assert.ok(
feed.store.dispatch.notCalled,
"PlacesFeed.store.dispatch not called"
);
// Now type a character.
fakeURLBar.listeners.keydown({ key: "f" });
Assert.ok(fakeURLBar.handoff.calledOnce, "gURLBar.handoff was called");
Assert.ok(fakeURLBar.handoff.calledWithExactly("", engine, SESSION_ID));
Assert.ok(
fakeURLBar.removeHiddenFocus.calledOnce,
"gURLBar.removeHiddenFocus was called"
);
Assert.ok(
feed.store.dispatch.calledOnce,
"PlacesFeed.store.dispatch called"
);
Assert.ok(
feed.store.dispatch.calledWith({
meta: {
from: "ActivityStream:Main",
skipMain: true,
to: "ActivityStream:Content",
toTarget: {},
},
type: "DISABLE_SEARCH",
})
);
sandbox.restore();
}
);
add_task(async function test_observe_dispatch_PLACES_LINK_BLOCKED() {
info(
"PlacesFeed.observe should dispatch a PLACES_LINK_BLOCKED action " +
"with the url of the blocked link"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
feed.observe(null, BLOCKED_EVENT, "foo123.com");
Assert.equal(
feed.store.dispatch.firstCall.args[0].type,
at.PLACES_LINK_BLOCKED
);
Assert.deepEqual(feed.store.dispatch.firstCall.args[0].data, {
url: "foo123.com",
});
sandbox.restore();
});
add_task(async function test_observe_no_dispatch() {
info(
"PlacesFeed.observe should not call dispatch if the topic is something " +
"other than BLOCKED_EVENT"
);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
feed.observe(null, "someotherevent");
Assert.ok(
feed.store.dispatch.notCalled,
"PlacesFeed.store.dispatch not called"
);
sandbox.restore();
});
add_task(
async function test_handlePlacesEvent_dispatch_one_PLACES_LINKS_CHANGED() {
let events = [
{
message:
"PlacesFeed.handlePlacesEvent should only dispatch 1 PLACES_LINKS_CHANGED action " +
"if many bookmark-added notifications happened at once",
dispatchCallCount: 5,
event: {
itemType: TYPE_BOOKMARK,
source: SOURCES.DEFAULT,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
isTagging: false,
type: "bookmark-added",
},
},
{
message:
"PlacesFeed.handlePlacesEvent should only dispatch 1 " +
"PLACES_LINKS_CHANGED action if many onItemRemoved notifications " +
"happened at once",
dispatchCallCount: 5,
event: {
id: null,
parentId: null,
index: null,
itemType: TYPE_BOOKMARK,
url: "foo.com",
guid: "rTU_oiklsU7D",
parentGuid: "2BzBQXOPFmuU",
source: SOURCES.DEFAULT,
type: "bookmark-removed",
},
},
{
message:
"PlacesFeed.handlePlacesEvent should only dispatch 1 " +
"PLACES_LINKS_CHANGED action if any page-removed notifications " +
"happened at once",
dispatchCallCount: 5,
event: {
type: "page-removed",
url: "foo.com",
isRemovedFromStore: true,
},
},
];
for (let { message, dispatchCallCount, event } of events) {
info(message);
let sandbox = sinon.createSandbox();
let feed = getPlacesFeedForTest(sandbox);
await feed.placesObserver.handlePlacesEvent([event]);
await feed.placesObserver.handlePlacesEvent([event]);
await feed.placesObserver.handlePlacesEvent([event]);
await feed.placesObserver.handlePlacesEvent([event]);
Assert.ok(feed.placesChangedTimer, "PlacesFeed dispatch timer created");
// Let's speed things up a bit.
feed.placesChangedTimer.delay = 0;
// Wait for the timer to go off and get cleared
await TestUtils.waitForCondition(
() => !feed.placesChangedTimer,
"PlacesFeed dispatch timer cleared"
);
Assert.equal(
feed.store.dispatch.callCount,
dispatchCallCount,
`PlacesFeed.store.dispatch was called ${dispatchCallCount} times`
);
Assert.ok(
feed.store.dispatch.withArgs(
ac.OnlyToMain({ type: at.PLACES_LINKS_CHANGED })
).calledOnce,
"PlacesFeed.store.dispatch called with PLACES_LINKS_CHANGED once"
);
sandbox.restore();
}
}
);
add_task(async function test_PlacesObserver_dispatches() {
let events = [
{
message:
"PlacesObserver should dispatch a PLACES_HISTORY_CLEARED action " +
"on history-cleared",
args: { type: "history-cleared" },
expectedAction: { type: at.PLACES_HISTORY_CLEARED },
},
{
message:
"PlacesObserver should dispatch a PLACES_LINKS_DELETED action " +
"with the right url",
args: {
type: "page-removed",
url: "foo.com",
isRemovedFromStore: true,
},
expectedAction: {
type: at.PLACES_LINKS_DELETED,
data: { urls: ["foo.com"] },
},
},
{
message:
"PlacesObserver should dispatch a PLACES_BOOKMARK_ADDED action with " +
"the bookmark data - http",
args: {
itemType: TYPE_BOOKMARK,
source: SOURCES.DEFAULT,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
isTagging: false,
type: "bookmark-added",
},
expectedAction: {
type: at.PLACES_BOOKMARK_ADDED,
data: {
bookmarkGuid: FAKE_BOOKMARK.bookmarkGuid,
bookmarkTitle: FAKE_BOOKMARK.bookmarkTitle,
dateAdded: FAKE_BOOKMARK.dateAdded * 1000,
},
},
},
{
message:
"PlacesObserver should dispatch a PLACES_BOOKMARK_ADDED action with " +
"the bookmark data - https",
args: {
itemType: TYPE_BOOKMARK,
source: SOURCES.DEFAULT,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
isTagging: false,
type: "bookmark-added",
},
expectedAction: {
type: at.PLACES_BOOKMARK_ADDED,
data: {
bookmarkGuid: FAKE_BOOKMARK.bookmarkGuid,
bookmarkTitle: FAKE_BOOKMARK.bookmarkTitle,
dateAdded: FAKE_BOOKMARK.dateAdded * 1000,
},
},
},
];
for (let { message, args, expectedAction } of events) {
info(message);
let sandbox = sinon.createSandbox();
let dispatch = sandbox.spy();
let observer = new PlacesObserver(dispatch);
await observer.handlePlacesEvent([args]);
Assert.ok(dispatch.calledWith(expectedAction));
sandbox.restore();
}
});
add_task(async function test_PlacesObserver_ignores() {
let events = [
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARK_ADDED action - " +
"not http/https for bookmark-added",
event: {
itemType: TYPE_BOOKMARK,
source: SOURCES.DEFAULT,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
url: "foo.com",
isTagging: false,
type: "bookmark-added",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARK_ADDED action - " +
"has IMPORT source for bookmark-added",
event: {
itemType: TYPE_BOOKMARK,
source: SOURCES.IMPORT,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
url: "foo.com",
isTagging: false,
type: "bookmark-added",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARK_ADDED " +
"action - has RESTORE source for bookmark-added",
event: {
itemType: TYPE_BOOKMARK,
source: SOURCES.RESTORE,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
url: "foo.com",
isTagging: false,
type: "bookmark-added",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARK_ADDED " +
"action - has RESTORE_ON_STARTUP source for bookmark-added",
event: {
itemType: TYPE_BOOKMARK,
source: SOURCES.RESTORE_ON_STARTUP,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
url: "foo.com",
isTagging: false,
type: "bookmark-added",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARK_ADDED " +
"action - has SYNC source for bookmark-added",
event: {
itemType: TYPE_BOOKMARK,
source: SOURCES.SYNC,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
url: "foo.com",
isTagging: false,
type: "bookmark-added",
},
},
{
message:
"PlacesObserver should ignore events that are not of " +
"TYPE_BOOKMARK for bookmark-added",
event: {
itemType: "nottypebookmark",
source: SOURCES.DEFAULT,
dateAdded: FAKE_BOOKMARK.dateAdded,
guid: FAKE_BOOKMARK.bookmarkGuid,
title: FAKE_BOOKMARK.bookmarkTitle,
isTagging: false,
type: "bookmark-added",
},
},
{
message:
"PlacesObserver should ignore events that are not of " +
"TYPE_BOOKMARK for bookmark-removed",
event: {
id: null,
parentId: null,
index: null,
itemType: "nottypebookmark",
url: null,
guid: "461Z_7daEqIh",
parentGuid: "hkHScG3aI3hh",
source: SOURCES.DEFAULT,
type: "bookmark-removed",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARKS_REMOVED " +
"action - has SYNC source for bookmark-removed",
event: {
id: null,
parentId: null,
index: null,
itemType: TYPE_BOOKMARK,
url: "foo.com",
guid: "uvRE3stjoZOI",
parentGuid: "BnsXZl8VMJjB",
source: SOURCES.SYNC,
type: "bookmark-removed",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARKS_REMOVED " +
"action - has IMPORT source for bookmark-removed",
event: {
id: null,
parentId: null,
index: null,
itemType: TYPE_BOOKMARK,
url: "foo.com",
guid: "VF6YwhGpHrOW",
parentGuid: "7Vz8v9nKcSoq",
source: SOURCES.IMPORT,
type: "bookmark-removed",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARKS_REMOVED " +
"action - has RESTORE source for bookmark-removed",
event: {
id: null,
parentId: null,
index: null,
itemType: TYPE_BOOKMARK,
url: "foo.com",
guid: "eKozFyXJP97R",
parentGuid: "ya8Z2FbjKnD0",
source: SOURCES.RESTORE,
type: "bookmark-removed",
},
},
{
message:
"PlacesObserver should not dispatch a PLACES_BOOKMARKS_REMOVED " +
"action - has RESTORE_ON_STARTUP source for bookmark-removed",
event: {
id: null,
parentId: null,
index: null,
itemType: TYPE_BOOKMARK,
url: "foo.com",
guid: "StSGMhrYYfyD",
parentGuid: "vL8wsCe2j_eT",
source: SOURCES.RESTORE_ON_STARTUP,
type: "bookmark-removed",
},
},
];
for (let { message, event } of events) {
info(message);
let sandbox = sinon.createSandbox();
let dispatch = sandbox.spy();
let observer = new PlacesObserver(dispatch);
await observer.handlePlacesEvent([event]);
Assert.ok(dispatch.notCalled, "PlacesObserver.dispatch not called");
sandbox.restore();
}
});
add_task(async function test_PlacesObserver_bookmark_removed() {
info(
"PlacesObserver should dispatch a PLACES_BOOKMARKS_REMOVED " +
"action with the right URL and bookmarkGuid for bookmark-removed"
);
let sandbox = sinon.createSandbox();
let dispatch = sandbox.spy();
let observer = new PlacesObserver(dispatch);
await observer.handlePlacesEvent([
{
id: null,
parentId: null,
index: null,
itemType: TYPE_BOOKMARK,
url: "foo.com",
guid: "Xgnxs27I9JnX",
parentGuid: "a4k739PL55sP",
source: SOURCES.DEFAULT,
type: "bookmark-removed",
},
]);
Assert.ok(
dispatch.calledWith({
type: at.PLACES_BOOKMARKS_REMOVED,
data: { urls: ["foo.com"] },
})
);
sandbox.restore();
});