summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/test/xpcshell/test_PlacesFeed.js')
-rw-r--r--browser/components/newtab/test/xpcshell/test_PlacesFeed.js1812
1 files changed, 1812 insertions, 0 deletions
diff --git a/browser/components/newtab/test/xpcshell/test_PlacesFeed.js b/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
new file mode 100644
index 0000000000..8e7c42d639
--- /dev/null
+++ b/browser/components/newtab/test/xpcshell/test_PlacesFeed.js
@@ -0,0 +1,1812 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { actionTypes: at, actionCreators: ac } = ChromeUtils.importESModule(
+ "resource://activity-stream/common/Actions.sys.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",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+ TestUtils: "resource://testing-common/TestUtils.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",
+ url: "http://example.com/test-123",
+ frecency: 0,
+ hidden: false,
+ visitCount: 0,
+ lastVisitDate: 0,
+ targetFolderGuid: null,
+ targetFolderItemId: -1,
+ targetFolderTitle: null,
+ }),
+ new PlacesBookmarkRemoved({
+ id: -1,
+ url: "http://example.com/test-123",
+ title: "test-123",
+ itemType: 1,
+ parentId: -2,
+ index: 0,
+ guid: "M3WYgJlm2Jlx",
+ parentGuid: "DO1f97R4KC3Y",
+ source: 0,
+ isTagging: false,
+ isDescendantRemoval: false,
+ }),
+ new PlacesHistoryCleared(),
+ new PlacesVisitRemoved({
+ url: "http://example.com/test-123",
+ 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, whereToOpenLink: e => "current" },
+ },
+ },
+ };
+ 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: e => "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,
+ "https://foo.com/ref"
+ );
+
+ 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: e => "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",
+ open_url: "https://getpocket.com/foo",
+ type: "pocket",
+ },
+ _target: {
+ browser: {
+ ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "current" },
+ },
+ },
+ };
+
+ 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, whereToOpenLink: e => "current" },
+ },
+ },
+ };
+
+ 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,
+ "https://getpocket.com/signup?utm_source=firefox_newtab_save_button&utm_campaign=slug&utm_content=branch-slug"
+ );
+ 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,
+ url: "https://www.foo.com",
+ 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,
+ url: "http://www.foo.com",
+ 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,
+ url: "http://www.foo.com",
+ },
+ },
+ },
+ {
+ 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,
+ url: "https://www.foo.com",
+ 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,
+ url: "https://www.foo.com",
+ },
+ },
+ },
+ ];
+
+ 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,
+ url: "https://www.foo.com",
+ 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();
+});