BookmarkPanelHub.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. import {_BookmarkPanelHub} from "lib/BookmarkPanelHub.jsm";
  2. import {GlobalOverrider} from "test/unit/utils";
  3. import {PanelTestProvider} from "lib/PanelTestProvider.jsm";
  4. describe("BookmarkPanelHub", () => {
  5. let globals;
  6. let sandbox;
  7. let instance;
  8. let fakeAddImpression;
  9. let fakeHandleMessageRequest;
  10. let fakeL10n;
  11. let fakeMessage;
  12. let fakeMessageFluent;
  13. let fakeTarget;
  14. let fakeContainer;
  15. let fakeDispatch;
  16. let fakeWindow;
  17. let isBrowserPrivateStub;
  18. beforeEach(() => {
  19. sandbox = sinon.createSandbox();
  20. globals = new GlobalOverrider();
  21. fakeL10n = {setAttributes: sandbox.stub()};
  22. globals.set("DOMLocalization", function() { return fakeL10n; }); // eslint-disable-line prefer-arrow-callback
  23. globals.set("FxAccounts", {config: {promiseEmailFirstURI: sandbox.stub()}});
  24. isBrowserPrivateStub = sandbox.stub().returns(false);
  25. globals.set("PrivateBrowsingUtils", {isBrowserPrivate: isBrowserPrivateStub});
  26. instance = new _BookmarkPanelHub();
  27. fakeAddImpression = sandbox.stub();
  28. fakeHandleMessageRequest = sandbox.stub();
  29. [{content: fakeMessageFluent}, {content: fakeMessage}] = PanelTestProvider.getMessages();
  30. fakeContainer = {
  31. addEventListener: sandbox.stub(),
  32. setAttribute: sandbox.stub(),
  33. removeAttribute: sandbox.stub(),
  34. classList: {add: sandbox.stub()},
  35. appendChild: sandbox.stub(),
  36. querySelector: sandbox.stub(),
  37. children: [],
  38. style: {},
  39. };
  40. fakeWindow = {
  41. ownerGlobal: {openLinkIn: sandbox.stub(), gBrowser: {selectedBrowser: "browser"}},
  42. MozXULElement: {insertFTLIfNeeded: sandbox.stub()},
  43. };
  44. const document = {
  45. createElementNS: sandbox.stub().returns(fakeContainer),
  46. getElementById: sandbox.stub().returns(fakeContainer),
  47. };
  48. fakeTarget = {
  49. document,
  50. container: {
  51. querySelector: sandbox.stub(),
  52. appendChild: sandbox.stub(),
  53. setAttribute: sandbox.stub(),
  54. removeAttribute: sandbox.stub(),
  55. },
  56. hidePopup: sandbox.stub(),
  57. infoButton: {},
  58. close: sandbox.stub(),
  59. browser: {ownerGlobal: {gBrowser: {ownerDocument: document}, window: fakeWindow}},
  60. };
  61. fakeDispatch = sandbox.stub();
  62. });
  63. afterEach(() => {
  64. instance.uninit();
  65. sandbox.restore();
  66. globals.restore();
  67. });
  68. it("should create an instance", () => {
  69. assert.ok(instance);
  70. });
  71. it("should uninit", () => {
  72. instance.uninit();
  73. assert.isFalse(instance._initialized);
  74. assert.isNull(instance._addImpression);
  75. assert.isNull(instance._handleMessageRequest);
  76. });
  77. it("should instantiate handleMessageRequest and addImpression and l10n", () => {
  78. instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
  79. assert.equal(instance._addImpression, fakeAddImpression);
  80. assert.equal(instance._handleMessageRequest, fakeHandleMessageRequest);
  81. assert.equal(instance._dispatch, fakeDispatch);
  82. assert.ok(instance._l10n);
  83. assert.isTrue(instance._initialized);
  84. });
  85. it("should return early if not initialized", async () => {
  86. assert.isFalse(await instance.messageRequest());
  87. });
  88. describe("#messageRequest", () => {
  89. beforeEach(() => {
  90. sandbox.stub(instance, "onResponse");
  91. instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
  92. });
  93. afterEach(() => {
  94. sandbox.restore();
  95. });
  96. it("should not re-request messages for the same URL", async () => {
  97. instance._response = {url: "foo.com", content: true};
  98. fakeTarget.url = "foo.com";
  99. sandbox.stub(instance, "showMessage");
  100. await instance.messageRequest(fakeTarget);
  101. assert.notCalled(fakeHandleMessageRequest);
  102. assert.calledOnce(instance.showMessage);
  103. });
  104. it("should call handleMessageRequest", async () => {
  105. fakeHandleMessageRequest.resolves(fakeMessage);
  106. await instance.messageRequest(fakeTarget, {});
  107. assert.calledOnce(fakeHandleMessageRequest);
  108. assert.calledWithExactly(fakeHandleMessageRequest, instance._trigger);
  109. });
  110. it("should call onResponse", async () => {
  111. fakeHandleMessageRequest.resolves(fakeMessage);
  112. await instance.messageRequest(fakeTarget, {});
  113. assert.calledOnce(instance.onResponse);
  114. assert.calledWithExactly(instance.onResponse, fakeMessage, fakeTarget, {});
  115. });
  116. });
  117. describe("#onResponse", () => {
  118. beforeEach(() => {
  119. instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
  120. sandbox.stub(instance, "showMessage");
  121. sandbox.stub(instance, "sendImpression");
  122. sandbox.stub(instance, "hideMessage");
  123. fakeTarget = {infoButton: {disabled: true}};
  124. });
  125. it("should show a message when called with a response", () => {
  126. instance.onResponse({content: "content"}, fakeTarget, fakeWindow);
  127. assert.calledOnce(instance.showMessage);
  128. assert.calledWithExactly(instance.showMessage, "content", fakeTarget, fakeWindow);
  129. assert.calledOnce(instance.sendImpression);
  130. });
  131. it("should insert the appropriate ftl files with translations", () => {
  132. instance.onResponse({content: "content"}, fakeTarget, fakeWindow);
  133. assert.calledTwice(fakeWindow.MozXULElement.insertFTLIfNeeded);
  134. assert.calledWith(fakeWindow.MozXULElement.insertFTLIfNeeded, "browser/newtab/asrouter.ftl");
  135. assert.calledWith(fakeWindow.MozXULElement.insertFTLIfNeeded, "browser/branding/sync-brand.ftl");
  136. });
  137. it("should dispatch a user impression", () => {
  138. sandbox.spy(instance, "sendUserEventTelemetry");
  139. instance.onResponse({content: "content"}, fakeTarget, fakeWindow);
  140. assert.calledOnce(instance.sendUserEventTelemetry);
  141. assert.calledWithExactly(instance.sendUserEventTelemetry, "IMPRESSION", fakeWindow);
  142. assert.calledOnce(fakeDispatch);
  143. const [ping] = fakeDispatch.firstCall.args;
  144. assert.equal(ping.type, "DOORHANGER_TELEMETRY");
  145. assert.equal(ping.data.event, "IMPRESSION");
  146. });
  147. it("should not dispatch a user impression if the window is private", () => {
  148. isBrowserPrivateStub.returns(true);
  149. sandbox.spy(instance, "sendUserEventTelemetry");
  150. instance.onResponse({content: "content"}, fakeTarget, fakeWindow);
  151. assert.calledOnce(instance.sendUserEventTelemetry);
  152. assert.calledWithExactly(instance.sendUserEventTelemetry, "IMPRESSION", fakeWindow);
  153. assert.notCalled(fakeDispatch);
  154. });
  155. it("should hide existing messages if no response is provided", () => {
  156. instance.onResponse(null, fakeTarget);
  157. assert.calledOnce(instance.hideMessage);
  158. assert.calledWithExactly(instance.hideMessage, fakeTarget);
  159. });
  160. });
  161. describe("#showMessage.collapsed=false", () => {
  162. beforeEach(() => {
  163. instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
  164. sandbox.stub(instance, "toggleRecommendation");
  165. sandbox.stub(instance, "_response").value({collapsed: false});
  166. });
  167. it("should create a container", () => {
  168. fakeTarget.container.querySelector.returns(false);
  169. instance.showMessage(fakeMessage, fakeTarget);
  170. assert.equal(fakeTarget.document.createElementNS.callCount, 5);
  171. assert.calledOnce(fakeTarget.container.appendChild);
  172. assert.notCalled(fakeL10n.setAttributes);
  173. });
  174. it("should create a container (fluent message)", () => {
  175. fakeTarget.container.querySelector.returns(false);
  176. instance.showMessage(fakeMessageFluent, fakeTarget);
  177. assert.equal(fakeTarget.document.createElementNS.callCount, 5);
  178. assert.calledOnce(fakeTarget.container.appendChild);
  179. });
  180. it("should set l10n attributes", () => {
  181. fakeTarget.container.querySelector.returns(false);
  182. instance.showMessage(fakeMessageFluent, fakeTarget);
  183. assert.equal(fakeL10n.setAttributes.callCount, 4);
  184. });
  185. it("should reuse the container", () => {
  186. fakeTarget.container.querySelector.returns(true);
  187. instance.showMessage(fakeMessage, fakeTarget);
  188. assert.notCalled(fakeTarget.container.appendChild);
  189. });
  190. it("should open a tab with FxA signup", async () => {
  191. fakeTarget.container.querySelector.returns(false);
  192. instance.showMessage(fakeMessage, fakeTarget, fakeWindow);
  193. // Call the event listener cb
  194. await fakeContainer.addEventListener.firstCall.args[1]();
  195. assert.calledOnce(fakeWindow.ownerGlobal.openLinkIn);
  196. });
  197. it("should send a click event", async () => {
  198. sandbox.stub(instance, "sendUserEventTelemetry");
  199. fakeTarget.container.querySelector.returns(false);
  200. instance.showMessage(fakeMessage, fakeTarget, fakeWindow);
  201. // Call the event listener cb
  202. await fakeContainer.addEventListener.firstCall.args[1]();
  203. assert.calledOnce(instance.sendUserEventTelemetry);
  204. assert.calledWithExactly(instance.sendUserEventTelemetry, "CLICK", fakeWindow);
  205. });
  206. it("should send a click event", async () => {
  207. sandbox.stub(instance, "sendUserEventTelemetry");
  208. fakeTarget.container.querySelector.returns(false);
  209. instance.showMessage(fakeMessage, fakeTarget, fakeWindow);
  210. // Call the event listener cb
  211. await fakeContainer.addEventListener.firstCall.args[1]();
  212. assert.calledOnce(instance.sendUserEventTelemetry);
  213. assert.calledWithExactly(instance.sendUserEventTelemetry, "CLICK", fakeWindow);
  214. });
  215. it("should collapse the message", () => {
  216. fakeTarget.container.querySelector.returns(false);
  217. sandbox.spy(instance, "collapseMessage");
  218. instance._response.collapsed = false;
  219. instance.showMessage(fakeMessage, fakeTarget, fakeWindow);
  220. // Show message calls it once so we need to reset
  221. instance.toggleRecommendation.reset();
  222. // Call the event listener cb
  223. fakeContainer.addEventListener.secondCall.args[1]();
  224. assert.calledOnce(instance.collapseMessage);
  225. assert.calledOnce(fakeTarget.close);
  226. assert.isTrue(instance._response.collapsed);
  227. assert.calledOnce(instance.toggleRecommendation);
  228. });
  229. it("should send a dismiss event", () => {
  230. sandbox.stub(instance, "sendUserEventTelemetry");
  231. sandbox.spy(instance, "collapseMessage");
  232. instance._response.collapsed = false;
  233. instance.showMessage(fakeMessage, fakeTarget, fakeWindow);
  234. // Call the event listener cb
  235. fakeContainer.addEventListener.secondCall.args[1]();
  236. assert.calledOnce(instance.sendUserEventTelemetry);
  237. assert.calledWithExactly(instance.sendUserEventTelemetry, "DISMISS", fakeWindow);
  238. });
  239. it("should call toggleRecommendation `true`", () => {
  240. instance.showMessage(fakeMessage, fakeTarget, fakeWindow);
  241. assert.calledOnce(instance.toggleRecommendation);
  242. assert.calledWithExactly(instance.toggleRecommendation, true);
  243. });
  244. });
  245. describe("#showMessage.collapsed=true", () => {
  246. beforeEach(() => {
  247. sandbox.stub(instance, "_response").value({collapsed: true, target: fakeTarget});
  248. sandbox.stub(instance, "toggleRecommendation");
  249. });
  250. it("should return early if the message is collapsed", () => {
  251. instance.showMessage();
  252. assert.calledOnce(instance.toggleRecommendation);
  253. assert.calledWithExactly(instance.toggleRecommendation, false);
  254. });
  255. });
  256. describe("#hideMessage", () => {
  257. let removeStub;
  258. beforeEach(() => {
  259. sandbox.stub(instance, "toggleRecommendation");
  260. removeStub = sandbox.stub();
  261. fakeTarget = {container: {querySelector: sandbox.stub().returns({remove: removeStub})}};
  262. });
  263. it("should remove the message", () => {
  264. instance.hideMessage(fakeTarget);
  265. assert.calledOnce(removeStub);
  266. });
  267. it("should call toggleRecommendation `false`", () => {
  268. instance.hideMessage(fakeTarget);
  269. assert.calledOnce(instance.toggleRecommendation);
  270. assert.calledWithExactly(instance.toggleRecommendation, false);
  271. });
  272. });
  273. describe("#toggleRecommendation", () => {
  274. let target;
  275. beforeEach(() => {
  276. target = {
  277. infoButton: {},
  278. container: {
  279. setAttribute: sandbox.stub(),
  280. removeAttribute: sandbox.stub(),
  281. },
  282. };
  283. sandbox.stub(instance, "_response").value({target});
  284. });
  285. it("should check infoButton", () => {
  286. instance.toggleRecommendation(true);
  287. assert.isTrue(target.infoButton.checked);
  288. });
  289. it("should uncheck infoButton", () => {
  290. instance.toggleRecommendation(false);
  291. assert.isFalse(target.infoButton.checked);
  292. });
  293. it("should uncheck infoButton", () => {
  294. target.infoButton.checked = true;
  295. instance.toggleRecommendation();
  296. assert.isFalse(target.infoButton.checked);
  297. });
  298. it("should disable the container", () => {
  299. target.infoButton.checked = true;
  300. instance.toggleRecommendation();
  301. assert.calledOnce(target.container.setAttribute);
  302. });
  303. it("should enable container", () => {
  304. target.infoButton.checked = false;
  305. instance.toggleRecommendation();
  306. assert.calledOnce(target.container.removeAttribute);
  307. });
  308. });
  309. describe("#_forceShowMessage", () => {
  310. it("should call showMessage with the correct args", () => {
  311. sandbox.spy(instance, "showMessage");
  312. sandbox.stub(instance, "hideMessage");
  313. instance._forceShowMessage(fakeTarget, {content: fakeMessage});
  314. assert.calledOnce(instance.showMessage);
  315. assert.calledOnce(instance.hideMessage);
  316. assert.calledWithExactly(instance.showMessage, fakeMessage, sinon.match.object, fakeWindow);
  317. });
  318. it("should insert required fluent files", () => {
  319. sandbox.stub(instance, "showMessage");
  320. instance._forceShowMessage(fakeTarget, {content: fakeMessage});
  321. assert.calledTwice(fakeWindow.MozXULElement.insertFTLIfNeeded);
  322. });
  323. it("should insert a message you can collapse", () => {
  324. sandbox.spy(instance, "showMessage");
  325. sandbox.stub(instance, "toggleRecommendation");
  326. sandbox.stub(instance, "sendUserEventTelemetry");
  327. instance._forceShowMessage(fakeTarget, {content: fakeMessage});
  328. const [, eventListenerCb] = fakeContainer.addEventListener.secondCall.args;
  329. // Called with `true` to show the message
  330. instance.toggleRecommendation.reset();
  331. eventListenerCb({stopPropagation: sandbox.stub()});
  332. assert.calledWithExactly(instance.toggleRecommendation, false);
  333. });
  334. });
  335. describe("#sendImpression", () => {
  336. beforeEach(() => {
  337. instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
  338. instance._response = "foo";
  339. });
  340. it("should dispatch an impression", () => {
  341. instance.sendImpression();
  342. assert.calledOnce(fakeAddImpression);
  343. assert.equal(fakeAddImpression.firstCall.args[0], "foo");
  344. });
  345. });
  346. });