From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../newtab/test/unit/lib/PlacesFeed.test.js | 1245 ++++++++++++++++++++ 1 file changed, 1245 insertions(+) create mode 100644 browser/components/newtab/test/unit/lib/PlacesFeed.test.js (limited to 'browser/components/newtab/test/unit/lib/PlacesFeed.test.js') diff --git a/browser/components/newtab/test/unit/lib/PlacesFeed.test.js b/browser/components/newtab/test/unit/lib/PlacesFeed.test.js new file mode 100644 index 0000000000..20210ab7b1 --- /dev/null +++ b/browser/components/newtab/test/unit/lib/PlacesFeed.test.js @@ -0,0 +1,1245 @@ +import { + actionCreators as ac, + actionTypes as at, +} from "common/Actions.sys.mjs"; +import { GlobalOverrider } from "test/unit/utils"; +import injector from "inject!lib/PlacesFeed.jsm"; + +const FAKE_BOOKMARK = { + bookmarkGuid: "xi31", + bookmarkTitle: "Foo", + dateAdded: 123214232, + url: "foo.com", +}; +const TYPE_BOOKMARK = 0; // This is fake, for testing +const SOURCES = { + DEFAULT: 0, + SYNC: 1, + IMPORT: 2, + RESTORE: 5, + RESTORE_ON_STARTUP: 6, +}; + +const BLOCKED_EVENT = "newtab-linkBlocked"; // The event dispatched in NewTabUtils when a link is blocked; + +const TOP_SITES_BLOCKED_SPONSORS_PREF = "browser.topsites.blockedSponsors"; +const POCKET_SITE_PREF = "extensions.pocket.site"; + +describe("PlacesFeed", () => { + let PlacesFeed; + let PlacesObserver; + let globals; + let sandbox; + let feed; + let shortURLStub; + beforeEach(() => { + globals = new GlobalOverrider(); + sandbox = globals.sandbox; + globals.set("NewTabUtils", { + activityStreamProvider: { getBookmark() {} }, + activityStreamLinks: { + addBookmark: sandbox.spy(), + deleteBookmark: sandbox.spy(), + deleteHistoryEntry: sandbox.spy(), + blockURL: sandbox.spy(), + addPocketEntry: sandbox.spy(() => Promise.resolve()), + deletePocketEntry: sandbox.spy(() => Promise.resolve()), + archivePocketEntry: sandbox.spy(() => Promise.resolve()), + }, + }); + globals.set("pktApi", { + isUserLoggedIn: sandbox.spy(), + }); + globals.set("ExperimentAPI", { + getExperiment: sandbox.spy(), + }); + globals.set("NimbusFeatures", { + pocketNewtab: { + getVariable: sandbox.spy(), + }, + }); + globals.set("PartnerLinkAttribution", { + makeRequest: sandbox.spy(), + }); + sandbox + .stub(global.PlacesUtils.bookmarks, "TYPE_BOOKMARK") + .value(TYPE_BOOKMARK); + sandbox.stub(global.PlacesUtils.bookmarks, "SOURCES").value(SOURCES); + sandbox.spy(global.PlacesUtils.history, "addObserver"); + sandbox.spy(global.PlacesUtils.history, "removeObserver"); + sandbox.spy(global.PlacesUtils.observers, "addListener"); + sandbox.spy(global.PlacesUtils.observers, "removeListener"); + sandbox.spy(global.Services.obs, "addObserver"); + sandbox.spy(global.Services.obs, "removeObserver"); + sandbox.spy(global.console, "error"); + shortURLStub = sandbox + .stub() + .callsFake(site => + site.url.replace(/(.com|.ca)/, "").replace("https://", "") + ); + + global.Services.io.newURI = spec => ({ + mutate: () => ({ + setRef: ref => ({ + finalize: () => ({ + ref, + spec, + }), + }), + }), + spec, + scheme: "https", + }); + + global.Cc["@mozilla.org/timer;1"] = { + createInstance() { + return { + initWithCallback: sinon.stub().callsFake(callback => callback()), + cancel: sinon.spy(), + }; + }, + }; + ({ PlacesFeed } = injector({ + "lib/ShortURL.jsm": { shortURL: shortURLStub }, + })); + PlacesObserver = PlacesFeed.PlacesObserver; + feed = new PlacesFeed(); + feed.store = { dispatch: sinon.spy() }; + globals.set("AboutNewTab", { + activityStream: { store: { feeds: { get() {} } } }, + }); + }); + afterEach(() => { + globals.restore(); + sandbox.restore(); + }); + + it("should have a PlacesObserver that dispatches to the store", () => { + assert.instanceOf(feed.placesObserver, PlacesObserver); + const action = { type: "FOO" }; + + feed.placesObserver.dispatch(action); + + assert.calledOnce(feed.store.dispatch); + assert.equal(feed.store.dispatch.firstCall.args[0].type, action.type); + }); + + describe("#addToBlockedTopSitesSponsors", () => { + let spy; + beforeEach(() => { + sandbox + .stub(global.Services.prefs, "getStringPref") + .withArgs(TOP_SITES_BLOCKED_SPONSORS_PREF) + .returns(`["foo","bar"]`); + spy = sandbox.spy(global.Services.prefs, "setStringPref"); + }); + + it("should add the blocked sponsors to the blocklist", () => { + feed.addToBlockedTopSitesSponsors([ + { url: "test.com" }, + { url: "test1.com" }, + ]); + + assert.calledOnce(spy); + const [, sponsors] = spy.firstCall.args; + assert.deepEqual( + new Set(["foo", "bar", "test", "test1"]), + new Set(JSON.parse(sponsors)) + ); + }); + + it("should not add duplicate sponsors to the blocklist", () => { + feed.addToBlockedTopSitesSponsors([ + { url: "foo.com" }, + { url: "bar.com" }, + { url: "test.com" }, + ]); + + assert.calledOnce(spy); + const [, sponsors] = spy.firstCall.args; + assert.deepEqual( + new Set(["foo", "bar", "test"]), + new Set(JSON.parse(sponsors)) + ); + }); + }); + + describe("#onAction", () => { + it("should add bookmark, history, places, blocked observers on INIT", () => { + feed.onAction({ type: at.INIT }); + + assert.calledWith( + global.PlacesUtils.observers.addListener, + [ + "bookmark-added", + "bookmark-removed", + "history-cleared", + "page-removed", + ], + feed.placesObserver.handlePlacesEvent + ); + assert.calledWith(global.Services.obs.addObserver, feed, BLOCKED_EVENT); + }); + it("should remove bookmark, history, places, blocked observers, and timers on UNINIT", () => { + feed.placesChangedTimer = + global.Cc["@mozilla.org/timer;1"].createInstance(); + let spy = feed.placesChangedTimer.cancel; + feed.onAction({ type: at.UNINIT }); + + assert.calledWith( + global.PlacesUtils.observers.removeListener, + [ + "bookmark-added", + "bookmark-removed", + "history-cleared", + "page-removed", + ], + feed.placesObserver.handlePlacesEvent + ); + assert.calledWith( + global.Services.obs.removeObserver, + feed, + BLOCKED_EVENT + ); + assert.equal(feed.placesChangedTimer, null); + assert.calledOnce(spy); + }); + it("should block a url on BLOCK_URL", () => { + feed.onAction({ + type: at.BLOCK_URL, + data: [{ url: "apple.com", pocket_id: 1234 }], + }); + assert.calledWith(global.NewTabUtils.activityStreamLinks.blockURL, { + url: "apple.com", + pocket_id: 1234, + }); + }); + it("should update the blocked top sites sponsors", () => { + sandbox.stub(feed, "addToBlockedTopSitesSponsors"); + feed.onAction({ + type: at.BLOCK_URL, + data: [{ url: "foo.com", pocket_id: 1234, isSponsoredTopSite: 1 }], + }); + assert.calledWith(feed.addToBlockedTopSitesSponsors, [ + { url: "foo.com" }, + ]); + }); + it("should bookmark a url on BOOKMARK_URL", () => { + const data = { url: "pear.com", title: "A pear" }; + const _target = { browser: { ownerGlobal() {} } }; + feed.onAction({ type: at.BOOKMARK_URL, data, _target }); + assert.calledWith( + global.NewTabUtils.activityStreamLinks.addBookmark, + data, + _target.browser.ownerGlobal + ); + }); + it("should delete a bookmark on DELETE_BOOKMARK_BY_ID", () => { + feed.onAction({ type: at.DELETE_BOOKMARK_BY_ID, data: "g123kd" }); + assert.calledWith( + global.NewTabUtils.activityStreamLinks.deleteBookmark, + "g123kd" + ); + }); + it("should delete a history entry on DELETE_HISTORY_URL", () => { + feed.onAction({ + type: at.DELETE_HISTORY_URL, + data: { url: "guava.com", forceBlock: null }, + }); + assert.calledWith( + global.NewTabUtils.activityStreamLinks.deleteHistoryEntry, + "guava.com" + ); + assert.notCalled(global.NewTabUtils.activityStreamLinks.blockURL); + }); + it("should delete a history entry on DELETE_HISTORY_URL and force a site to be blocked if specified", () => { + feed.onAction({ + type: at.DELETE_HISTORY_URL, + data: { url: "guava.com", forceBlock: "g123kd" }, + }); + assert.calledWith( + global.NewTabUtils.activityStreamLinks.deleteHistoryEntry, + "guava.com" + ); + assert.calledWith(global.NewTabUtils.activityStreamLinks.blockURL, { + url: "guava.com", + pocket_id: undefined, + }); + }); + it("should call openTrustedLinkIn with the correct url, where and params on OPEN_NEW_WINDOW", () => { + const openTrustedLinkIn = sinon.stub(); + const openWindowAction = { + type: at.OPEN_NEW_WINDOW, + data: { url: "https://foo.com" }, + _target: { browser: { ownerGlobal: { openTrustedLinkIn } } }, + }; + + feed.onAction(openWindowAction); + + assert.calledOnce(openTrustedLinkIn); + const [url, where, params] = openTrustedLinkIn.firstCall.args; + assert.equal(url, "https://foo.com"); + assert.equal(where, "window"); + assert.propertyVal(params, "private", false); + assert.propertyVal(params, "forceForeground", false); + }); + it("should call openTrustedLinkIn with the correct url, where, params and privacy args on OPEN_PRIVATE_WINDOW", () => { + const openTrustedLinkIn = sinon.stub(); + const openWindowAction = { + type: at.OPEN_PRIVATE_WINDOW, + data: { url: "https://foo.com" }, + _target: { browser: { ownerGlobal: { openTrustedLinkIn } } }, + }; + + feed.onAction(openWindowAction); + + assert.calledOnce(openTrustedLinkIn); + const [url, where, params] = openTrustedLinkIn.firstCall.args; + assert.equal(url, "https://foo.com"); + assert.equal(where, "window"); + assert.propertyVal(params, "private", true); + assert.propertyVal(params, "forceForeground", false); + }); + it("should call openTrustedLinkIn with the correct url, where and params on OPEN_LINK", () => { + const openTrustedLinkIn = sinon.stub(); + const openLinkAction = { + type: at.OPEN_LINK, + data: { url: "https://foo.com" }, + _target: { + browser: { + ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "current" }, + }, + }, + }; + + feed.onAction(openLinkAction); + + assert.calledOnce(openTrustedLinkIn); + const [url, where, params] = openTrustedLinkIn.firstCall.args; + assert.equal(url, "https://foo.com"); + assert.equal(where, "current"); + assert.propertyVal(params, "private", false); + assert.propertyVal(params, "forceForeground", false); + }); + it("should open link with referrer on OPEN_LINK", () => { + const openTrustedLinkIn = sinon.stub(); + const 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); + + const [, , params] = openTrustedLinkIn.firstCall.args; + assert.nestedPropertyVal(params, "referrerInfo.referrerPolicy", 5); + assert.nestedPropertyVal( + params, + "referrerInfo.originalReferrer.spec", + "https://foo.com/ref" + ); + }); + it("should mark link with typed bonus as typed before opening OPEN_LINK", () => { + const callOrder = []; + sinon + .stub(global.PlacesUtils.history, "markPageAsTyped") + .callsFake(() => { + callOrder.push("markPageAsTyped"); + }); + const openTrustedLinkIn = sinon.stub().callsFake(() => { + callOrder.push("openTrustedLinkIn"); + }); + const openLinkAction = { + type: at.OPEN_LINK, + data: { + typedBonus: true, + url: "https://foo.com", + }, + _target: { + browser: { + ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "tab" }, + }, + }, + }; + + feed.onAction(openLinkAction); + + assert.sameOrderedMembers(callOrder, [ + "markPageAsTyped", + "openTrustedLinkIn", + ]); + }); + it("should open the pocket link if it's a pocket story on OPEN_LINK", () => { + const openTrustedLinkIn = sinon.stub(); + const openLinkAction = { + type: at.OPEN_LINK, + data: { + url: "https://foo.com", + open_url: "getpocket.com/foo", + type: "pocket", + }, + _target: { + browser: { + ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "current" }, + }, + }, + }; + + feed.onAction(openLinkAction); + + assert.calledOnce(openTrustedLinkIn); + const [url, where, params] = openTrustedLinkIn.firstCall.args; + assert.equal(url, "getpocket.com/foo"); + assert.equal(where, "current"); + assert.propertyVal(params, "private", false); + }); + it("should not open link if not http", () => { + const openTrustedLinkIn = sinon.stub(); + global.Services.io.newURI = spec => ({ + mutate: () => ({ + setRef: ref => ({ + finalize: () => ({ + ref, + spec, + }), + }), + }), + spec, + scheme: "file", + }); + const openLinkAction = { + type: at.OPEN_LINK, + data: { url: "file:///foo.com" }, + _target: { + browser: { + ownerGlobal: { openTrustedLinkIn, whereToOpenLink: e => "current" }, + }, + }, + }; + + feed.onAction(openLinkAction); + const [e] = global.console.error.firstCall.args; + assert.equal( + e.message, + "Can't open link using file protocol from the new tab page." + ); + }); + it("should call fillSearchTopSiteTerm on FILL_SEARCH_TERM", () => { + sinon.stub(feed, "fillSearchTopSiteTerm"); + + feed.onAction({ type: at.FILL_SEARCH_TERM }); + + assert.calledOnce(feed.fillSearchTopSiteTerm); + }); + it("should call openTrustedLinkIn with the correct SUMO url on ABOUT_SPONSORED_TOP_SITES", () => { + const openTrustedLinkIn = sinon.stub(); + const openLinkAction = { + type: at.ABOUT_SPONSORED_TOP_SITES, + _target: { + browser: { + ownerGlobal: { openTrustedLinkIn }, + }, + }, + }; + + feed.onAction(openLinkAction); + + assert.calledOnce(openTrustedLinkIn); + const [url, where] = openTrustedLinkIn.firstCall.args; + assert.equal(url.endsWith("sponsor-privacy"), true); + assert.equal(where, "tab"); + }); + it("should set the URL bar value to the label value", async () => { + const locationBar = { search: sandbox.stub() }; + const action = { + type: at.FILL_SEARCH_TERM, + data: { label: "@Foo" }, + _target: { browser: { ownerGlobal: { gURLBar: locationBar } } }, + }; + + await feed.fillSearchTopSiteTerm(action); + + assert.calledOnce(locationBar.search); + assert.calledWithExactly(locationBar.search, "@Foo", { + searchEngine: null, + searchModeEntry: "topsites_newtab", + }); + }); + it("should call saveToPocket on SAVE_TO_POCKET", () => { + const action = { + type: at.SAVE_TO_POCKET, + data: { site: { url: "raspberry.com", title: "raspberry" } }, + _target: { browser: {} }, + }; + sinon.stub(feed, "saveToPocket"); + feed.onAction(action); + assert.calledWithExactly( + feed.saveToPocket, + action.data.site, + action._target.browser + ); + }); + it("should openTrustedLinkIn with sendToPocket if not logged in", () => { + const openTrustedLinkIn = sinon.stub(); + global.NimbusFeatures.pocketNewtab.getVariable = sandbox + .stub() + .returns(true); + global.pktApi.isUserLoggedIn = sandbox.stub().returns(false); + global.ExperimentAPI.getExperiment = sandbox.stub().returns({ + slug: "slug", + branch: { slug: "branch-slug" }, + }); + sandbox + .stub(global.Services.prefs, "getStringPref") + .withArgs(POCKET_SITE_PREF) + .returns("getpocket.com"); + const action = { + type: at.SAVE_TO_POCKET, + data: { site: { url: "raspberry.com", title: "raspberry" } }, + _target: { + browser: { + ownerGlobal: { + openTrustedLinkIn, + }, + }, + }, + }; + feed.onAction(action); + assert.calledOnce(openTrustedLinkIn); + const [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"); + }); + it("should call NewTabUtils.activityStreamLinks.addPocketEntry if we are saving a pocket story", async () => { + const action = { + data: { site: { url: "raspberry.com", title: "raspberry" } }, + _target: { browser: {} }, + }; + await feed.saveToPocket(action.data.site, action._target.browser); + assert.calledOnce(global.NewTabUtils.activityStreamLinks.addPocketEntry); + assert.calledWithExactly( + global.NewTabUtils.activityStreamLinks.addPocketEntry, + action.data.site.url, + action.data.site.title, + action._target.browser + ); + }); + it("should reject the promise if NewTabUtils.activityStreamLinks.addPocketEntry rejects", async () => { + const e = new Error("Error"); + const action = { + data: { site: { url: "raspberry.com", title: "raspberry" } }, + _target: { browser: {} }, + }; + global.NewTabUtils.activityStreamLinks.addPocketEntry = sandbox + .stub() + .rejects(e); + await feed.saveToPocket(action.data.site, action._target.browser); + assert.calledWith(global.console.error, e); + }); + it("should broadcast to content if we successfully added a link to Pocket", async () => { + // test in the form that the API returns data based on: https://getpocket.com/developer/docs/v3/add + global.NewTabUtils.activityStreamLinks.addPocketEntry = sandbox + .stub() + .resolves({ item: { open_url: "pocket.com/itemID", item_id: 1234 } }); + const action = { + data: { site: { url: "raspberry.com", title: "raspberry" } }, + _target: { browser: {} }, + }; + await feed.saveToPocket(action.data.site, action._target.browser); + 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", + }); + }); + it("should only broadcast if we got some data back from addPocketEntry", async () => { + global.NewTabUtils.activityStreamLinks.addPocketEntry = sandbox + .stub() + .resolves(null); + const action = { + data: { site: { url: "raspberry.com", title: "raspberry" } }, + _target: { browser: {} }, + }; + await feed.saveToPocket(action.data.site, action._target.browser); + assert.notCalled(feed.store.dispatch); + }); + it("should call deleteFromPocket on DELETE_FROM_POCKET", () => { + sandbox.stub(feed, "deleteFromPocket"); + feed.onAction({ + type: at.DELETE_FROM_POCKET, + data: { pocket_id: 12345 }, + }); + + assert.calledOnce(feed.deleteFromPocket); + assert.calledWithExactly(feed.deleteFromPocket, 12345); + }); + it("should catch if deletePocketEntry throws", async () => { + const e = new Error("Error"); + global.NewTabUtils.activityStreamLinks.deletePocketEntry = sandbox + .stub() + .rejects(e); + await feed.deleteFromPocket(12345); + + assert.calledWith(global.console.error, e); + }); + it("should call NewTabUtils.deletePocketEntry and dispatch POCKET_LINK_DELETED_OR_ARCHIVED when deleting from Pocket", async () => { + await feed.deleteFromPocket(12345); + + assert.calledOnce( + global.NewTabUtils.activityStreamLinks.deletePocketEntry + ); + assert.calledWith( + global.NewTabUtils.activityStreamLinks.deletePocketEntry, + 12345 + ); + + assert.calledOnce(feed.store.dispatch); + assert.calledWith(feed.store.dispatch, { + type: at.POCKET_LINK_DELETED_OR_ARCHIVED, + }); + }); + it("should call archiveFromPocket on ARCHIVE_FROM_POCKET", async () => { + sandbox.stub(feed, "archiveFromPocket"); + await feed.onAction({ + type: at.ARCHIVE_FROM_POCKET, + data: { pocket_id: 12345 }, + }); + + assert.calledOnce(feed.archiveFromPocket); + assert.calledWithExactly(feed.archiveFromPocket, 12345); + }); + it("should catch if archiveFromPocket throws", async () => { + const e = new Error("Error"); + global.NewTabUtils.activityStreamLinks.archivePocketEntry = sandbox + .stub() + .rejects(e); + await feed.archiveFromPocket(12345); + + assert.calledWith(global.console.error, e); + }); + it("should call NewTabUtils.archivePocketEntry and dispatch POCKET_LINK_DELETED_OR_ARCHIVED when archiving from Pocket", async () => { + await feed.archiveFromPocket(12345); + + assert.calledOnce( + global.NewTabUtils.activityStreamLinks.archivePocketEntry + ); + assert.calledWith( + global.NewTabUtils.activityStreamLinks.archivePocketEntry, + 12345 + ); + + assert.calledOnce(feed.store.dispatch); + assert.calledWith(feed.store.dispatch, { + type: at.POCKET_LINK_DELETED_OR_ARCHIVED, + }); + }); + it("should call handoffSearchToAwesomebar on HANDOFF_SEARCH_TO_AWESOMEBAR", () => { + const action = { + type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, + data: { text: "f" }, + meta: { fromTarget: {} }, + _target: { browser: { ownerGlobal: { gURLBar: { focus: () => {} } } } }, + }; + sinon.stub(feed, "handoffSearchToAwesomebar"); + feed.onAction(action); + assert.calledWith(feed.handoffSearchToAwesomebar, action); + }); + it("should call makeAttributionRequest on PARTNER_LINK_ATTRIBUTION", () => { + sinon.stub(feed, "makeAttributionRequest"); + let data = { targetURL: "https://partnersite.com", source: "topsites" }; + feed.onAction({ + type: at.PARTNER_LINK_ATTRIBUTION, + data, + }); + + assert.calledOnce(feed.makeAttributionRequest); + assert.calledWithExactly(feed.makeAttributionRequest, data); + }); + it("should call PartnerLinkAttribution.makeRequest when calling makeAttributionRequest", () => { + let data = { targetURL: "https://partnersite.com", source: "topsites" }; + feed.makeAttributionRequest(data); + assert.calledOnce(global.PartnerLinkAttribution.makeRequest); + }); + }); + + describe("handoffSearchToAwesomebar", () => { + let fakeUrlBar; + let listeners; + + beforeEach(() => { + fakeUrlBar = { + focus: sinon.spy(), + handoff: sinon.spy(), + setHiddenFocus: sinon.spy(), + removeHiddenFocus: sinon.spy(), + addEventListener: (ev, cb) => { + listeners[ev] = cb; + }, + removeEventListener: sinon.spy(), + }; + listeners = {}; + }); + it("should properly handle handoff with no text passed in", () => { + feed.handoffSearchToAwesomebar({ + _target: { browser: { ownerGlobal: { gURLBar: fakeUrlBar } } }, + data: {}, + meta: { fromTarget: {} }, + }); + assert.calledOnce(fakeUrlBar.setHiddenFocus); + assert.notCalled(fakeUrlBar.handoff); + assert.notCalled(feed.store.dispatch); + + // Now type a character. + listeners.keydown({ key: "f" }); + assert.calledOnce(fakeUrlBar.handoff); + assert.calledOnce(fakeUrlBar.removeHiddenFocus); + assert.calledOnce(feed.store.dispatch); + assert.calledWith(feed.store.dispatch, { + meta: { + from: "ActivityStream:Main", + skipMain: true, + to: "ActivityStream:Content", + toTarget: {}, + }, + type: "DISABLE_SEARCH", + }); + }); + it("should properly handle handoff with text data passed in", () => { + const sessionId = "decafc0ffee"; + sandbox + .stub(global.AboutNewTab.activityStream.store.feeds, "get") + .returns({ + sessions: { + get: () => { + return { session_id: sessionId }; + }, + }, + }); + feed.handoffSearchToAwesomebar({ + _target: { browser: { ownerGlobal: { gURLBar: fakeUrlBar } } }, + data: { text: "foo" }, + meta: { fromTarget: {} }, + }); + assert.calledOnce(fakeUrlBar.handoff); + assert.calledWithExactly( + fakeUrlBar.handoff, + "foo", + global.Services.search.defaultEngine, + sessionId + ); + assert.notCalled(fakeUrlBar.focus); + assert.notCalled(fakeUrlBar.setHiddenFocus); + + // Now call blur listener. + listeners.blur(); + assert.calledOnce(feed.store.dispatch); + assert.calledWith(feed.store.dispatch, { + meta: { + from: "ActivityStream:Main", + skipMain: true, + to: "ActivityStream:Content", + toTarget: {}, + }, + type: "SHOW_SEARCH", + }); + }); + it("should properly handle handoff with text data passed in, in private browsing mode", () => { + global.PrivateBrowsingUtils.isBrowserPrivate = () => true; + feed.handoffSearchToAwesomebar({ + _target: { browser: { ownerGlobal: { gURLBar: fakeUrlBar } } }, + data: { text: "foo" }, + meta: { fromTarget: {} }, + }); + assert.calledOnce(fakeUrlBar.handoff); + assert.calledWithExactly( + fakeUrlBar.handoff, + "foo", + global.Services.search.defaultPrivateEngine, + undefined + ); + assert.notCalled(fakeUrlBar.focus); + assert.notCalled(fakeUrlBar.setHiddenFocus); + + // Now call blur listener. + listeners.blur(); + assert.calledOnce(feed.store.dispatch); + assert.calledWith(feed.store.dispatch, { + meta: { + from: "ActivityStream:Main", + skipMain: true, + to: "ActivityStream:Content", + toTarget: {}, + }, + type: "SHOW_SEARCH", + }); + global.PrivateBrowsingUtils.isBrowserPrivate = () => false; + }); + it("should SHOW_SEARCH on ESC keydown", () => { + feed.handoffSearchToAwesomebar({ + _target: { browser: { ownerGlobal: { gURLBar: fakeUrlBar } } }, + data: { text: "foo" }, + meta: { fromTarget: {} }, + }); + assert.calledOnce(fakeUrlBar.handoff); + assert.calledWithExactly( + fakeUrlBar.handoff, + "foo", + global.Services.search.defaultEngine, + undefined + ); + assert.notCalled(fakeUrlBar.focus); + + // Now call ESC keydown. + listeners.keydown({ key: "Escape" }); + assert.calledOnce(feed.store.dispatch); + assert.calledWith(feed.store.dispatch, { + meta: { + from: "ActivityStream:Main", + skipMain: true, + to: "ActivityStream:Content", + toTarget: {}, + }, + type: "SHOW_SEARCH", + }); + }); + it("should properly handoff a newtab session id with no text passed in", () => { + const sessionId = "decafc0ffee"; + sandbox + .stub(global.AboutNewTab.activityStream.store.feeds, "get") + .returns({ + sessions: { + get: () => { + return { session_id: sessionId }; + }, + }, + }); + feed.handoffSearchToAwesomebar({ + _target: { browser: { ownerGlobal: { gURLBar: fakeUrlBar } } }, + data: {}, + meta: { fromTarget: {} }, + }); + assert.calledOnce(fakeUrlBar.setHiddenFocus); + assert.notCalled(fakeUrlBar.handoff); + assert.notCalled(feed.store.dispatch); + + // Now type a character. + listeners.keydown({ key: "f" }); + assert.calledOnce(fakeUrlBar.handoff); + assert.calledWithExactly( + fakeUrlBar.handoff, + "", + global.Services.search.defaultEngine, + sessionId + ); + assert.calledOnce(fakeUrlBar.removeHiddenFocus); + assert.calledOnce(feed.store.dispatch); + assert.calledWith(feed.store.dispatch, { + meta: { + from: "ActivityStream:Main", + skipMain: true, + to: "ActivityStream:Content", + toTarget: {}, + }, + type: "DISABLE_SEARCH", + }); + }); + }); + + describe("#observe", () => { + it("should dispatch a PLACES_LINK_BLOCKED action with the url of the blocked link", () => { + 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", + }); + }); + it("should not call dispatch if the topic is something other than BLOCKED_EVENT", () => { + feed.observe(null, "someotherevent"); + assert.notCalled(feed.store.dispatch); + }); + }); + + describe("Custom dispatch", () => { + it("should only dispatch 1 PLACES_LINKS_CHANGED action if many bookmark-added notifications happened at once", async () => { + // Yes, onItemAdded has at least 8 arguments. See function definition for docs. + const 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", + }, + ]; + await feed.placesObserver.handlePlacesEvent(args); + await feed.placesObserver.handlePlacesEvent(args); + await feed.placesObserver.handlePlacesEvent(args); + await feed.placesObserver.handlePlacesEvent(args); + assert.calledOnce( + feed.store.dispatch.withArgs( + ac.OnlyToMain({ type: at.PLACES_LINKS_CHANGED }) + ) + ); + }); + it("should only dispatch 1 PLACES_LINKS_CHANGED action if many onItemRemoved notifications happened at once", async () => { + const args = [ + { + id: null, + parentId: null, + index: null, + itemType: TYPE_BOOKMARK, + url: "foo.com", + guid: "123foo", + parentGuid: "", + source: SOURCES.DEFAULT, + type: "bookmark-removed", + }, + ]; + await feed.placesObserver.handlePlacesEvent(args); + await feed.placesObserver.handlePlacesEvent(args); + await feed.placesObserver.handlePlacesEvent(args); + await feed.placesObserver.handlePlacesEvent(args); + + assert.calledOnce( + feed.store.dispatch.withArgs( + ac.OnlyToMain({ type: at.PLACES_LINKS_CHANGED }) + ) + ); + }); + it("should only dispatch 1 PLACES_LINKS_CHANGED action if any page-removed notifications happened at once", async () => { + await feed.placesObserver.handlePlacesEvent([ + { type: "page-removed", url: "foo.com", isRemovedFromStore: true }, + ]); + await feed.placesObserver.handlePlacesEvent([ + { type: "page-removed", url: "foo1.com", isRemovedFromStore: true }, + ]); + await feed.placesObserver.handlePlacesEvent([ + { type: "page-removed", url: "foo2.com", isRemovedFromStore: true }, + ]); + + assert.calledOnce( + feed.store.dispatch.withArgs( + ac.OnlyToMain({ type: at.PLACES_LINKS_CHANGED }) + ) + ); + }); + }); + + describe("PlacesObserver", () => { + let dispatch; + let observer; + beforeEach(() => { + dispatch = sandbox.spy(); + observer = new PlacesObserver(dispatch); + }); + + describe("#history-cleared", () => { + it("should dispatch a PLACES_HISTORY_CLEARED action", async () => { + const args = [{ type: "history-cleared" }]; + await observer.handlePlacesEvent(args); + assert.calledWith(dispatch, { type: at.PLACES_HISTORY_CLEARED }); + }); + }); + + describe("#page-removed", () => { + it("should dispatch a PLACES_LINKS_DELETED action with the right url", async () => { + const args = [ + { + type: "page-removed", + url: "foo.com", + isRemovedFromStore: true, + }, + ]; + await observer.handlePlacesEvent(args); + assert.calledWith(dispatch, { + type: at.PLACES_LINKS_DELETED, + data: { urls: ["foo.com"] }, + }); + }); + }); + + describe("#bookmark-added", () => { + it("should dispatch a PLACES_BOOKMARK_ADDED action with the bookmark data - http", async () => { + const 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.calledWith(dispatch.secondCall, { + type: at.PLACES_BOOKMARK_ADDED, + data: { + bookmarkGuid: FAKE_BOOKMARK.bookmarkGuid, + bookmarkTitle: FAKE_BOOKMARK.bookmarkTitle, + dateAdded: FAKE_BOOKMARK.dateAdded * 1000, + url: "http://www.foo.com", + }, + }); + }); + it("should dispatch a PLACES_BOOKMARK_ADDED action with the bookmark data - https", async () => { + const 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.calledWith(dispatch.secondCall, { + type: at.PLACES_BOOKMARK_ADDED, + data: { + bookmarkGuid: FAKE_BOOKMARK.bookmarkGuid, + bookmarkTitle: FAKE_BOOKMARK.bookmarkTitle, + dateAdded: FAKE_BOOKMARK.dateAdded * 1000, + url: "https://www.foo.com", + }, + }); + }); + it("should not dispatch a PLACES_BOOKMARK_ADDED action - not http/https", async () => { + const args = [ + { + 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARK_ADDED action - has IMPORT source", async () => { + const args = [ + { + 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARK_ADDED action - has RESTORE source", async () => { + const args = [ + { + 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARK_ADDED action - has RESTORE_ON_STARTUP source", async () => { + const args = [ + { + 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARK_ADDED action - has SYNC source", async () => { + const args = [ + { + 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should ignore events that are not of TYPE_BOOKMARK", async () => { + const args = [ + { + 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", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + }); + describe("#bookmark-removed", () => { + it("should ignore events that are not of TYPE_BOOKMARK", async () => { + const args = [ + { + id: null, + parentId: null, + index: null, + itemType: "nottypebookmark", + url: null, + guid: "123foo", + parentGuid: "", + source: SOURCES.DEFAULT, + type: "bookmark-removed", + }, + ]; + await observer.handlePlacesEvent(args); + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARKS_REMOVED action - has SYNC source", async () => { + const args = [ + { + id: null, + parentId: null, + index: null, + itemType: TYPE_BOOKMARK, + url: "foo.com", + guid: "123foo", + parentGuid: "", + source: SOURCES.SYNC, + type: "bookmark-removed", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARKS_REMOVED action - has IMPORT source", async () => { + const args = [ + { + id: null, + parentId: null, + index: null, + itemType: TYPE_BOOKMARK, + url: "foo.com", + guid: "123foo", + parentGuid: "", + source: SOURCES.IMPORT, + type: "bookmark-removed", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARKS_REMOVED action - has RESTORE source", async () => { + const args = [ + { + id: null, + parentId: null, + index: null, + itemType: TYPE_BOOKMARK, + url: "foo.com", + guid: "123foo", + parentGuid: "", + source: SOURCES.RESTORE, + type: "bookmark-removed", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should not dispatch a PLACES_BOOKMARKS_REMOVED action - has RESTORE_ON_STARTUP source", async () => { + const args = [ + { + id: null, + parentId: null, + index: null, + itemType: TYPE_BOOKMARK, + url: "foo.com", + guid: "123foo", + parentGuid: "", + source: SOURCES.RESTORE_ON_STARTUP, + type: "bookmark-removed", + }, + ]; + await observer.handlePlacesEvent(args); + + assert.notCalled(dispatch); + }); + it("should dispatch a PLACES_BOOKMARKS_REMOVED action with the right URL and bookmarkGuid", async () => { + const args = [ + { + id: null, + parentId: null, + index: null, + itemType: TYPE_BOOKMARK, + url: "foo.com", + guid: "123foo", + parentGuid: "", + source: SOURCES.DEFAULT, + type: "bookmark-removed", + }, + ]; + await observer.handlePlacesEvent(args); + assert.calledWith(dispatch, { + type: at.PLACES_BOOKMARKS_REMOVED, + data: { urls: ["foo.com"] }, + }); + }); + }); + }); +}); -- cgit v1.2.3