asrouter-content.test.jsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. import {ASRouterUISurface, ASRouterUtils} from "content-src/asrouter/asrouter-content";
  2. import {GlobalOverrider, mountWithIntl} from "test/unit/utils";
  3. import {OUTGOING_MESSAGE_NAME as AS_GENERAL_OUTGOING_MESSAGE_NAME} from "content-src/lib/init-store";
  4. import {FAKE_LOCAL_MESSAGES} from "./constants";
  5. import {OnboardingMessageProvider} from "lib/OnboardingMessageProvider.jsm";
  6. import React from "react";
  7. import {Trailhead} from "../../../content-src/asrouter/templates/Trailhead/Trailhead";
  8. let [FAKE_MESSAGE] = FAKE_LOCAL_MESSAGES;
  9. const FAKE_NEWSLETTER_SNIPPET = FAKE_LOCAL_MESSAGES.find(msg => msg.id === "newsletter");
  10. const FAKE_FXA_SNIPPET = FAKE_LOCAL_MESSAGES.find(msg => msg.id === "fxa");
  11. const FAKE_BELOW_SEARCH_SNIPPET = FAKE_LOCAL_MESSAGES.find(msg => msg.id === "belowsearch");
  12. FAKE_MESSAGE = Object.assign({}, FAKE_MESSAGE, {provider: "fakeprovider"});
  13. const FAKE_BUNDLED_MESSAGE = {bundle: [{id: "foo", template: "onboarding", content: {title: "Foo", primary_button: {label: "Bar"}, text: "Foo123"}}], extraTemplateStrings: {}, template: "onboarding"};
  14. describe("ASRouterUtils", () => {
  15. let global;
  16. let sandbox;
  17. let fakeSendAsyncMessage;
  18. beforeEach(() => {
  19. global = new GlobalOverrider();
  20. sandbox = sinon.createSandbox();
  21. fakeSendAsyncMessage = sandbox.stub();
  22. global.set({RPMSendAsyncMessage: fakeSendAsyncMessage});
  23. });
  24. afterEach(() => {
  25. sandbox.restore();
  26. global.restore();
  27. });
  28. it("should send a message with the right payload data", () => {
  29. ASRouterUtils.sendTelemetry({id: 1, event: "CLICK"});
  30. assert.calledOnce(fakeSendAsyncMessage);
  31. assert.calledWith(fakeSendAsyncMessage, AS_GENERAL_OUTGOING_MESSAGE_NAME);
  32. const [, payload] = fakeSendAsyncMessage.firstCall.args;
  33. assert.propertyVal(payload.data, "id", 1);
  34. assert.propertyVal(payload.data, "event", "CLICK");
  35. });
  36. });
  37. describe("ASRouterUISurface", () => {
  38. let wrapper;
  39. let global;
  40. let sandbox;
  41. let headerPortal;
  42. let footerPortal;
  43. let fakeDocument;
  44. beforeEach(() => {
  45. sandbox = sinon.createSandbox();
  46. headerPortal = document.createElement("div");
  47. footerPortal = document.createElement("div");
  48. sandbox.stub(footerPortal, "querySelector").returns(footerPortal);
  49. fakeDocument = {
  50. location: {href: ""},
  51. _listeners: new Set(),
  52. _visibilityState: "hidden",
  53. get visibilityState() {
  54. return this._visibilityState;
  55. },
  56. set visibilityState(value) {
  57. if (this._visibilityState === value) {
  58. return;
  59. }
  60. this._visibilityState = value;
  61. this._listeners.forEach(l => l());
  62. },
  63. addEventListener(event, listener) {
  64. this._listeners.add(listener);
  65. },
  66. removeEventListener(event, listener) {
  67. this._listeners.delete(listener);
  68. },
  69. get body() {
  70. return document.createElement("body");
  71. },
  72. getElementById(id) {
  73. return id === "header-asrouter-container" ? headerPortal : footerPortal;
  74. },
  75. };
  76. global = new GlobalOverrider();
  77. global.set({
  78. RPMAddMessageListener: sandbox.stub(),
  79. RPMRemoveMessageListener: sandbox.stub(),
  80. RPMSendAsyncMessage: sandbox.stub(),
  81. });
  82. sandbox.stub(ASRouterUtils, "sendTelemetry");
  83. wrapper = mountWithIntl(<ASRouterUISurface document={fakeDocument} />);
  84. });
  85. afterEach(() => {
  86. sandbox.restore();
  87. global.restore();
  88. });
  89. it("should render the component if a message id is defined", () => {
  90. wrapper.setState({message: FAKE_MESSAGE});
  91. assert.isTrue(wrapper.exists());
  92. });
  93. it("should pass in the correct form_method for newsletter snippets", () => {
  94. wrapper.setState({message: FAKE_NEWSLETTER_SNIPPET});
  95. assert.isTrue(wrapper.find("SubmitFormSnippet").exists());
  96. assert.propertyVal(wrapper.find("SubmitFormSnippet").props(), "form_method", "POST");
  97. });
  98. it("should pass in the correct form_method for fxa snippets", () => {
  99. wrapper.setState({message: FAKE_FXA_SNIPPET});
  100. assert.isTrue(wrapper.find("SubmitFormSnippet").exists());
  101. assert.propertyVal(wrapper.find("SubmitFormSnippet").props(), "form_method", "GET");
  102. });
  103. it("should render the component if a bundle of messages is defined", () => {
  104. wrapper.setState({bundle: FAKE_BUNDLED_MESSAGE});
  105. assert.isTrue(wrapper.exists());
  106. });
  107. it("should render a preview banner if message provider is preview", () => {
  108. wrapper.setState({message: {...FAKE_MESSAGE, provider: "preview"}});
  109. assert.isTrue(wrapper.find(".snippets-preview-banner").exists());
  110. });
  111. it("should not render a preview banner if message provider is not preview", () => {
  112. wrapper.setState({message: FAKE_MESSAGE});
  113. assert.isFalse(wrapper.find(".snippets-preview-banner").exists());
  114. });
  115. it("should render a SimpleSnippet in the footer portal", () => {
  116. wrapper.setState({message: FAKE_MESSAGE});
  117. assert.isTrue(footerPortal.childElementCount > 0);
  118. assert.equal(headerPortal.childElementCount, 0);
  119. });
  120. it("should not render a SimpleBelowSearchSnippet in a portal", () => {
  121. wrapper.setState({message: FAKE_BELOW_SEARCH_SNIPPET});
  122. assert.equal(headerPortal.childElementCount, 0);
  123. assert.equal(footerPortal.childElementCount, 0);
  124. });
  125. it("should render a trailhead message in the header portal", async () => {
  126. const message = (await OnboardingMessageProvider.getUntranslatedMessages()).find(msg => msg.template === "trailhead");
  127. wrapper.setState({message});
  128. assert.isTrue(headerPortal.childElementCount > 0);
  129. assert.equal(footerPortal.childElementCount, 0);
  130. });
  131. it("should dispatch an event to select the correct theme", () => {
  132. const stub = sandbox.stub(window, "dispatchEvent");
  133. sandbox.stub(ASRouterUtils, "getPreviewEndpoint").returns({theme: "dark"});
  134. wrapper = mountWithIntl(<ASRouterUISurface document={fakeDocument} />);
  135. assert.calledOnce(stub);
  136. assert.property(stub.firstCall.args[0].detail.data, "ntp_background");
  137. assert.property(stub.firstCall.args[0].detail.data, "ntp_text");
  138. assert.property(stub.firstCall.args[0].detail.data, "sidebar");
  139. assert.property(stub.firstCall.args[0].detail.data, "sidebar_text");
  140. });
  141. describe("snippets", () => {
  142. it("should send correct event and source when snippet is blocked", () => {
  143. wrapper.setState({message: FAKE_MESSAGE});
  144. wrapper.find(".blockButton").simulate("click");
  145. assert.propertyVal(ASRouterUtils.sendTelemetry.firstCall.args[0], "event", "BLOCK");
  146. assert.propertyVal(ASRouterUtils.sendTelemetry.firstCall.args[0], "source", "NEWTAB_FOOTER_BAR");
  147. });
  148. it("should not send telemetry when a preview snippet is blocked", () => {
  149. wrapper.setState({message: {...FAKE_MESSAGE, provider: "preview"}});
  150. wrapper.find(".blockButton").simulate("click");
  151. assert.notCalled(ASRouterUtils.sendTelemetry);
  152. });
  153. });
  154. describe("trailhead", () => {
  155. it("should render trailhead if a trailhead message is received", async () => {
  156. const message = (await OnboardingMessageProvider.getUntranslatedMessages()).find(msg => msg.template === "trailhead");
  157. wrapper.setState({message});
  158. assert.lengthOf(wrapper.find(Trailhead), 1);
  159. });
  160. });
  161. describe("impressions", () => {
  162. function simulateVisibilityChange(value) {
  163. fakeDocument.visibilityState = value;
  164. }
  165. it("should call blockById after CTA link is clicked", () => {
  166. wrapper.setState({message: FAKE_MESSAGE});
  167. sandbox.stub(ASRouterUtils, "blockById");
  168. wrapper.instance().sendClick({target: {dataset: {metric: ""}}});
  169. assert.calledOnce(ASRouterUtils.blockById);
  170. assert.calledWithExactly(ASRouterUtils.blockById, FAKE_MESSAGE.id);
  171. });
  172. it("should executeAction if defined on the anchor", () => {
  173. wrapper.setState({message: FAKE_MESSAGE});
  174. sandbox.spy(ASRouterUtils, "executeAction");
  175. wrapper.instance().sendClick({target: {dataset: {action: "OPEN_MENU", args: "appMenu"}}});
  176. assert.calledOnce(ASRouterUtils.executeAction);
  177. assert.calledWithExactly(ASRouterUtils.executeAction, {type: "OPEN_MENU", data: {args: "appMenu"}});
  178. });
  179. it("should not call blockById if do_not_autoblock is true", () => {
  180. wrapper.setState({message: {...FAKE_MESSAGE, ...{content: {...FAKE_MESSAGE.content, do_not_autoblock: true}}}});
  181. sandbox.stub(ASRouterUtils, "blockById");
  182. wrapper.instance().sendClick({target: {dataset: {metric: ""}}});
  183. assert.notCalled(ASRouterUtils.blockById);
  184. });
  185. it("should not send an impression if no message exists", () => {
  186. simulateVisibilityChange("visible");
  187. assert.notCalled(ASRouterUtils.sendTelemetry);
  188. });
  189. it("should not send an impression if the page is not visible", () => {
  190. simulateVisibilityChange("hidden");
  191. wrapper.setState({message: FAKE_MESSAGE});
  192. assert.notCalled(ASRouterUtils.sendTelemetry);
  193. });
  194. it("should not send an impression for a preview message", () => {
  195. wrapper.setState({message: {...FAKE_MESSAGE, provider: "preview"}});
  196. assert.notCalled(ASRouterUtils.sendTelemetry);
  197. simulateVisibilityChange("visible");
  198. assert.notCalled(ASRouterUtils.sendTelemetry);
  199. });
  200. it("should send an impression ping when there is a message and the page becomes visible", () => {
  201. wrapper.setState({message: FAKE_MESSAGE});
  202. assert.notCalled(ASRouterUtils.sendTelemetry);
  203. simulateVisibilityChange("visible");
  204. assert.calledOnce(ASRouterUtils.sendTelemetry);
  205. });
  206. it("should send the correct impression source", () => {
  207. wrapper.setState({message: FAKE_MESSAGE});
  208. simulateVisibilityChange("visible");
  209. assert.calledOnce(ASRouterUtils.sendTelemetry);
  210. assert.propertyVal(ASRouterUtils.sendTelemetry.firstCall.args[0], "event", "IMPRESSION");
  211. assert.propertyVal(ASRouterUtils.sendTelemetry.firstCall.args[0], "source", "NEWTAB_FOOTER_BAR");
  212. });
  213. it("should send an impression ping when the page is visible and a message gets loaded", () => {
  214. simulateVisibilityChange("visible");
  215. wrapper.setState({message: {}});
  216. assert.notCalled(ASRouterUtils.sendTelemetry);
  217. wrapper.setState({message: FAKE_MESSAGE});
  218. assert.calledOnce(ASRouterUtils.sendTelemetry);
  219. });
  220. it("should send another impression ping if the message id changes", () => {
  221. simulateVisibilityChange("visible");
  222. wrapper.setState({message: FAKE_MESSAGE});
  223. assert.calledOnce(ASRouterUtils.sendTelemetry);
  224. wrapper.setState({message: FAKE_LOCAL_MESSAGES[1]});
  225. assert.calledTwice(ASRouterUtils.sendTelemetry);
  226. });
  227. it("should not send another impression ping if the message id has not changed", () => {
  228. simulateVisibilityChange("visible");
  229. wrapper.setState({message: FAKE_MESSAGE});
  230. assert.calledOnce(ASRouterUtils.sendTelemetry);
  231. wrapper.setState({somethingElse: 123});
  232. assert.calledOnce(ASRouterUtils.sendTelemetry);
  233. });
  234. it("should not send another impression ping if the message is cleared", () => {
  235. simulateVisibilityChange("visible");
  236. wrapper.setState({message: FAKE_MESSAGE});
  237. assert.calledOnce(ASRouterUtils.sendTelemetry);
  238. wrapper.setState({message: {}});
  239. assert.calledOnce(ASRouterUtils.sendTelemetry);
  240. });
  241. it("should call .sendTelemetry with the right message data", () => {
  242. simulateVisibilityChange("visible");
  243. wrapper.setState({message: FAKE_MESSAGE});
  244. assert.calledOnce(ASRouterUtils.sendTelemetry);
  245. const [payload] = ASRouterUtils.sendTelemetry.firstCall.args;
  246. assert.propertyVal(payload, "message_id", FAKE_MESSAGE.id);
  247. assert.propertyVal(payload, "event", "IMPRESSION");
  248. assert.propertyVal(payload, "action", `${FAKE_MESSAGE.provider}_user_event`);
  249. assert.propertyVal(payload, "source", "NEWTAB_FOOTER_BAR");
  250. });
  251. it("should call .sendTelemetry with the right message data when a bundle is dismissed", () => {
  252. wrapper.instance().dismissBundle([{id: 1}, {id: 2}, {id: 3}])();
  253. assert.calledOnce(ASRouterUtils.sendTelemetry);
  254. assert.calledWith(ASRouterUtils.sendTelemetry, {
  255. action: "onboarding_user_event",
  256. event: "DISMISS",
  257. id: "onboarding-cards",
  258. message_id: "1,2,3",
  259. source: "onboarding-cards",
  260. });
  261. });
  262. });
  263. });