import { ASRouterUISurface } from "content-src/asrouter/asrouter-content"; import { ASRouterUtils } from "content-src/asrouter/asrouter-utils"; import { GlobalOverrider } from "test/unit/utils"; import { FAKE_LOCAL_MESSAGES } from "./constants"; import React from "react"; import { mount } from "enzyme"; let [FAKE_MESSAGE] = FAKE_LOCAL_MESSAGES; const FAKE_NEWSLETTER_SNIPPET = FAKE_LOCAL_MESSAGES.find( msg => msg.id === "newsletter" ); const FAKE_FXA_SNIPPET = FAKE_LOCAL_MESSAGES.find(msg => msg.id === "fxa"); const FAKE_BELOW_SEARCH_SNIPPET = FAKE_LOCAL_MESSAGES.find( msg => msg.id === "belowsearch" ); FAKE_MESSAGE = Object.assign({}, FAKE_MESSAGE, { provider: "fakeprovider" }); describe("ASRouterUtils", () => { let globalOverrider; let sandbox; let globals; beforeEach(() => { globalOverrider = new GlobalOverrider(); sandbox = sinon.createSandbox(); globals = { ASRouterMessage: sandbox.stub(), }; globalOverrider.set(globals); }); afterEach(() => { sandbox.restore(); globalOverrider.restore(); }); it("should send a message with the right payload data", () => { ASRouterUtils.sendTelemetry({ id: 1, event: "CLICK" }); assert.calledOnce(globals.ASRouterMessage); assert.calledWith(globals.ASRouterMessage, { type: "AS_ROUTER_TELEMETRY_USER_EVENT", meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" }, data: { id: 1, event: "CLICK", }, }); }); }); describe("ASRouterUISurface", () => { let wrapper; let globalOverrider; let sandbox; let headerPortal; let footerPortal; let root; let fakeDocument; let globals; beforeEach(() => { sandbox = sinon.createSandbox(); headerPortal = document.createElement("div"); footerPortal = document.createElement("div"); root = document.createElement("div"); sandbox.stub(footerPortal, "querySelector").returns(footerPortal); fakeDocument = { location: { href: "" }, _listeners: new Set(), _visibilityState: "hidden", head: { appendChild(el) { return el; }, }, get visibilityState() { return this._visibilityState; }, set visibilityState(value) { if (this._visibilityState === value) { return; } this._visibilityState = value; this._listeners.forEach(l => l()); }, addEventListener(event, listener) { this._listeners.add(listener); }, removeEventListener(event, listener) { this._listeners.delete(listener); }, get body() { return document.createElement("body"); }, getElementById(id) { switch (id) { case "header-asrouter-container": return headerPortal; case "root": return root; default: return footerPortal; } }, createElement(tag) { return document.createElement(tag); }, }; globals = { ASRouterMessage: sandbox.stub().resolves(), ASRouterAddParentListener: sandbox.stub(), ASRouterRemoveParentListener: sandbox.stub(), fetch: sandbox.stub().resolves({ ok: true, status: 200, json: () => Promise.resolve({}), }), }; globalOverrider = new GlobalOverrider(); globalOverrider.set(globals); sandbox.stub(ASRouterUtils, "sendTelemetry"); wrapper = mount(); }); afterEach(() => { sandbox.restore(); globalOverrider.restore(); }); it("should render the component if a message id is defined", () => { wrapper.setState({ message: FAKE_MESSAGE }); assert.isTrue(wrapper.exists()); }); it("should pass in the correct form_method for newsletter snippets", () => { wrapper.setState({ message: FAKE_NEWSLETTER_SNIPPET }); assert.isTrue(wrapper.find("SubmitFormSnippet").exists()); assert.propertyVal( wrapper.find("SubmitFormSnippet").props(), "form_method", "POST" ); }); it("should pass in the correct form_method for fxa snippets", () => { wrapper.setState({ message: FAKE_FXA_SNIPPET }); assert.isTrue(wrapper.find("SubmitFormSnippet").exists()); assert.propertyVal( wrapper.find("SubmitFormSnippet").props(), "form_method", "GET" ); }); it("should render a preview banner if message provider is preview", () => { wrapper.setState({ message: { ...FAKE_MESSAGE, provider: "preview" } }); assert.isTrue(wrapper.find(".snippets-preview-banner").exists()); }); it("should not render a preview banner if message provider is not preview", () => { wrapper.setState({ message: FAKE_MESSAGE }); assert.isFalse(wrapper.find(".snippets-preview-banner").exists()); }); it("should render a SimpleSnippet in the footer portal", () => { wrapper.setState({ message: FAKE_MESSAGE }); assert.isTrue(footerPortal.childElementCount > 0); assert.equal(headerPortal.childElementCount, 0); }); it("should not render a SimpleBelowSearchSnippet in a portal", () => { wrapper.setState({ message: FAKE_BELOW_SEARCH_SNIPPET }); assert.equal(headerPortal.childElementCount, 0); assert.equal(footerPortal.childElementCount, 0); }); it("should dispatch an event to select the correct theme", () => { const stub = sandbox.stub(window, "dispatchEvent"); sandbox .stub(ASRouterUtils, "getPreviewEndpoint") .returns({ theme: "dark" }); wrapper = mount(); assert.calledOnce(stub); assert.property(stub.firstCall.args[0].detail.data, "ntp_background"); assert.property(stub.firstCall.args[0].detail.data, "ntp_text"); assert.property(stub.firstCall.args[0].detail.data, "sidebar"); assert.property(stub.firstCall.args[0].detail.data, "sidebar_text"); }); it("should set `dir=rtl` on the page's element if the dir param is set", () => { assert.notPropertyVal(fakeDocument, "dir", "rtl"); sandbox.stub(ASRouterUtils, "getPreviewEndpoint").returns({ dir: "rtl" }); wrapper = mount(); assert.propertyVal(fakeDocument, "dir", "rtl"); }); describe("snippets", () => { it("should send correct event and source when snippet is blocked", () => { wrapper.setState({ message: FAKE_MESSAGE }); wrapper.find(".blockButton").simulate("click"); assert.propertyVal( ASRouterUtils.sendTelemetry.firstCall.args[0], "event", "BLOCK" ); assert.propertyVal( ASRouterUtils.sendTelemetry.firstCall.args[0], "source", "NEWTAB_FOOTER_BAR" ); }); it("should not send telemetry when a preview snippet is blocked", () => { wrapper.setState({ message: { ...FAKE_MESSAGE, provider: "preview" } }); wrapper.find(".blockButton").simulate("click"); assert.notCalled(ASRouterUtils.sendTelemetry); }); }); describe("impressions", () => { function simulateVisibilityChange(value) { fakeDocument.visibilityState = value; } it("should call blockById after CTA link is clicked", () => { wrapper.setState({ message: FAKE_MESSAGE }); sandbox.stub(ASRouterUtils, "blockById").resolves(); wrapper.instance().sendClick({ target: { dataset: { metric: "" } } }); assert.calledOnce(ASRouterUtils.blockById); assert.calledWith(ASRouterUtils.blockById, FAKE_MESSAGE.id); }); it("should executeAction if defined on the anchor", () => { wrapper.setState({ message: FAKE_MESSAGE }); sandbox.spy(ASRouterUtils, "executeAction"); wrapper.instance().sendClick({ target: { dataset: { action: "OPEN_MENU", args: "appMenu" } }, }); assert.calledOnce(ASRouterUtils.executeAction); assert.calledWithExactly(ASRouterUtils.executeAction, { type: "OPEN_MENU", data: { args: "appMenu" }, }); }); it("should not call blockById if do_not_autoblock is true", () => { wrapper.setState({ message: { ...FAKE_MESSAGE, ...{ content: { ...FAKE_MESSAGE.content, do_not_autoblock: true } }, }, }); sandbox.stub(ASRouterUtils, "blockById"); wrapper.instance().sendClick({ target: { dataset: { metric: "" } } }); assert.notCalled(ASRouterUtils.blockById); }); it("should not send an impression if no message exists", () => { simulateVisibilityChange("visible"); assert.notCalled(ASRouterUtils.sendTelemetry); }); it("should not send an impression if the page is not visible", () => { simulateVisibilityChange("hidden"); wrapper.setState({ message: FAKE_MESSAGE }); assert.notCalled(ASRouterUtils.sendTelemetry); }); it("should not send an impression for a preview message", () => { wrapper.setState({ message: { ...FAKE_MESSAGE, provider: "preview" } }); assert.notCalled(ASRouterUtils.sendTelemetry); simulateVisibilityChange("visible"); assert.notCalled(ASRouterUtils.sendTelemetry); }); it("should send an impression ping when there is a message and the page becomes visible", () => { wrapper.setState({ message: FAKE_MESSAGE }); assert.notCalled(ASRouterUtils.sendTelemetry); simulateVisibilityChange("visible"); assert.calledOnce(ASRouterUtils.sendTelemetry); }); it("should send the correct impression source", () => { wrapper.setState({ message: FAKE_MESSAGE }); simulateVisibilityChange("visible"); assert.calledOnce(ASRouterUtils.sendTelemetry); assert.propertyVal( ASRouterUtils.sendTelemetry.firstCall.args[0], "event", "IMPRESSION" ); assert.propertyVal( ASRouterUtils.sendTelemetry.firstCall.args[0], "source", "NEWTAB_FOOTER_BAR" ); }); it("should send an impression ping when the page is visible and a message gets loaded", () => { simulateVisibilityChange("visible"); wrapper.setState({ message: {} }); assert.notCalled(ASRouterUtils.sendTelemetry); wrapper.setState({ message: FAKE_MESSAGE }); assert.calledOnce(ASRouterUtils.sendTelemetry); }); it("should send another impression ping if the message id changes", () => { simulateVisibilityChange("visible"); wrapper.setState({ message: FAKE_MESSAGE }); assert.calledOnce(ASRouterUtils.sendTelemetry); wrapper.setState({ message: FAKE_LOCAL_MESSAGES[1] }); assert.calledTwice(ASRouterUtils.sendTelemetry); }); it("should not send another impression ping if the message id has not changed", () => { simulateVisibilityChange("visible"); wrapper.setState({ message: FAKE_MESSAGE }); assert.calledOnce(ASRouterUtils.sendTelemetry); wrapper.setState({ somethingElse: 123 }); assert.calledOnce(ASRouterUtils.sendTelemetry); }); it("should not send another impression ping if the message is cleared", () => { simulateVisibilityChange("visible"); wrapper.setState({ message: FAKE_MESSAGE }); assert.calledOnce(ASRouterUtils.sendTelemetry); wrapper.setState({ message: {} }); assert.calledOnce(ASRouterUtils.sendTelemetry); }); it("should call .sendTelemetry with the right message data", () => { simulateVisibilityChange("visible"); wrapper.setState({ message: FAKE_MESSAGE }); assert.calledOnce(ASRouterUtils.sendTelemetry); const [payload] = ASRouterUtils.sendTelemetry.firstCall.args; assert.propertyVal(payload, "message_id", FAKE_MESSAGE.id); assert.propertyVal(payload, "event", "IMPRESSION"); assert.propertyVal( payload, "action", `${FAKE_MESSAGE.provider}_user_event` ); assert.propertyVal(payload, "source", "NEWTAB_FOOTER_BAR"); }); it("should construct a OPEN_ABOUT_PAGE action with attribution", () => { wrapper.setState({ message: FAKE_MESSAGE }); const stub = sandbox.stub(ASRouterUtils, "executeAction"); wrapper.instance().sendClick({ target: { dataset: { metric: "", entrypoint_value: "snippet", action: "OPEN_PREFERENCES_PAGE", args: "home", }, }, }); assert.calledOnce(stub); assert.calledWithExactly(stub, { type: "OPEN_PREFERENCES_PAGE", data: { args: "home", entrypoint: "snippet" }, }); }); it("should construct a OPEN_ABOUT_PAGE action with attribution", () => { wrapper.setState({ message: FAKE_MESSAGE }); const stub = sandbox.stub(ASRouterUtils, "executeAction"); wrapper.instance().sendClick({ target: { dataset: { metric: "", entrypoint_name: "entryPoint", entrypoint_value: "snippet", action: "OPEN_ABOUT_PAGE", args: "logins", }, }, }); assert.calledOnce(stub); assert.calledWithExactly(stub, { type: "OPEN_ABOUT_PAGE", data: { args: "logins", entrypoint: "entryPoint=snippet" }, }); }); }); describe(".fetchFlowParams", () => { let dispatchStub; const assertCalledWithURL = url => assert.calledWith(globals.fetch, new URL(url).toString(), { credentials: "omit", }); beforeEach(() => { dispatchStub = sandbox.stub(); wrapper = mount( ); }); it("should use the base url returned from the endpoint pref", async () => { wrapper = mount( ); await wrapper.instance().fetchFlowParams(); assertCalledWithURL("https://foo.com/metrics-flow"); }); it("should add given search params to the URL", async () => { const params = { foo: "1", bar: "2" }; await wrapper.instance().fetchFlowParams(params); assertCalledWithURL( "https://accounts.firefox.com/metrics-flow?foo=1&bar=2" ); }); it("should return flowId, flowBeginTime, deviceId on a 200 response", async () => { const flowInfo = { flowId: "foo", flowBeginTime: 123, deviceId: "bar" }; globals.fetch .withArgs("https://accounts.firefox.com/metrics-flow") .resolves({ ok: true, status: 200, json: () => Promise.resolve(flowInfo), }); const result = await wrapper.instance().fetchFlowParams(); assert.deepEqual(result, flowInfo); }); describe(".onUserAction", () => { it("if the action.type is ENABLE_FIREFOX_MONITOR, it should generate the right monitor URL given some flowParams", async () => { const flowInfo = { flowId: "foo", flowBeginTime: 123, deviceId: "bar" }; globals.fetch .withArgs( "https://accounts.firefox.com/metrics-flow?utm_term=avocado" ) .resolves({ ok: true, status: 200, json: () => Promise.resolve(flowInfo), }); sandbox.spy(ASRouterUtils, "executeAction"); const msg = { type: "ENABLE_FIREFOX_MONITOR", data: { args: { url: "https://monitor.firefox.com?foo=bar", flowRequestParams: { utm_term: "avocado", }, }, }, }; await wrapper.instance().onUserAction(msg); assertCalledWithURL( "https://accounts.firefox.com/metrics-flow?utm_term=avocado" ); assert.calledWith(ASRouterUtils.executeAction, { type: "OPEN_URL", data: { args: new URL( "https://monitor.firefox.com?foo=bar&deviceId=bar&flowId=foo&flowBeginTime=123" ).toString(), }, }); }); it("if the action.type is not ENABLE_FIREFOX_MONITOR, it should just call ASRouterUtils.executeAction", async () => { const msg = { type: "FOO", data: { args: "bar", }, }; sandbox.spy(ASRouterUtils, "executeAction"); await wrapper.instance().onUserAction(msg); assert.calledWith(ASRouterUtils.executeAction, msg); }); }); }); });