diff --git a/lib/ASRouter.jsm b/lib/ASRouter.jsm index b1e565a337..dc4b7d24fb 100644 --- a/lib/ASRouter.jsm +++ b/lib/ASRouter.jsm @@ -1487,7 +1487,12 @@ class _ASRouter { } else { await this.setState({ lastMessageId: message ? message.id : null }); } - await this._sendMessageToTarget(message, target, trigger); + await this._sendMessageToTarget( + message, + target, + trigger, + Boolean(previewMsgs.length) + ); } handleMessageRequest({ triggerId, template, returnAll = false }) { diff --git a/lib/BookmarkPanelHub.jsm b/lib/BookmarkPanelHub.jsm index b6b21a0484..84537bceb2 100644 --- a/lib/BookmarkPanelHub.jsm +++ b/lib/BookmarkPanelHub.jsm @@ -19,6 +19,19 @@ ChromeUtils.defineModuleGetter( "resource://gre/modules/PrivateBrowsingUtils.jsm" ); +const MARKUP_IDENTIFIERS = { + messageHeader: "editBookmarkPanelRecommendationTitle", + headerClass: "cfrMessageHeader", + contentParagraph: "editBookmarkPanelRecommendationContent", + ctaBtn: "editBookmarkPanelRecommendationCta", + // Div that contains header, message and cta + messageContainer: "cfrMessageContainer", + // Bookmark panel element where the messageContainer will be attached + panelAnchor: "editBookmarkPanelRecommendation", + // Bookmark panel info button used to toggle the message + infoButton: "editBookmarkPanelInfoButton", +}; + class _BookmarkPanelHub { constructor() { this._id = "BookmarkPanelHub"; @@ -27,8 +40,7 @@ class _BookmarkPanelHub { this._addImpression = null; this._dispatch = null; this._initialized = false; - this._response = null; - this._l10n = null; + this._state = null; this.messageRequest = this.messageRequest.bind(this); this.toggleRecommendation = this.toggleRecommendation.bind(this); @@ -45,17 +57,16 @@ class _BookmarkPanelHub { this._handleMessageRequest = handleMessageRequest; this._addImpression = addImpression; this._dispatch = dispatch; - this._l10n = new DOMLocalization(); this._initialized = true; + this._state = {}; } uninit() { - this._l10n = null; this._initialized = false; this._handleMessageRequest = null; this._addImpression = null; this._dispatch = null; - this._response = null; + this._state = null; } /** @@ -72,48 +83,45 @@ class _BookmarkPanelHub { return false; } - if ( - this._response && - this._response.win === win && - this._response.url === target.url && - this._response.content - ) { - this.showMessage(this._response.content, target, win); + if (this._state.url === target.url && this._state.message) { + this.showMessage(target, win, this._state.message.content); return true; } // If we didn't match on a previously cached request then make sure // the container is empty - this._removeContainer(target); + this._removeContainer(target, win); const response = await this._handleMessageRequest({ triggerId: this._trigger.id, }); - return this.onResponse(response, target, win); + return this.onResponse(target, win, response); + } + + insertFTLIfNeeded(win) { + // Only insert localization files if we need to show a message + win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl"); + win.MozXULElement.insertFTLIfNeeded("browser/branding/sync-brand.ftl"); } /** * If the response contains a message render it and send an impression. * Otherwise we remove the message from the container. */ - onResponse(response, target, win) { - this._response = { - ...response, + onResponse(target, win, response) { + this._state = { + message: response, collapsed: false, - target, - win, url: target.url, }; if (response && response.content) { - // Only insert localization files if we need to show a message - win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl"); - win.MozXULElement.insertFTLIfNeeded("browser/branding/sync-brand.ftl"); - this.showMessage(response.content, target, win); + this.insertFTLIfNeeded(win); + this.showMessage(target, win, response.content); this.sendImpression(); this.sendUserEventTelemetry("IMPRESSION", win); } else { - this.hideMessage(target); + this.removeMessage(target, win); } target.infoButton.disabled = !response; @@ -121,20 +129,23 @@ class _BookmarkPanelHub { return !!response; } - showMessage(message, target, win) { - if (this._response && this._response.collapsed) { - this.toggleRecommendation(false); + showMessage(target, win, message) { + const { document } = win; + if (this._state.collapsed) { + this.toggleRecommendation(target, false); return; } const createElement = elem => target.document.createElementNS("http://www.w3.org/1999/xhtml", elem); - let recommendation = target.container.querySelector("#cfrMessageContainer"); + let recommendation = document.getElementById( + MARKUP_IDENTIFIERS.messageContainer + ); if (!recommendation) { recommendation = createElement("div"); const headerContainer = createElement("div"); - headerContainer.classList.add("cfrMessageHeader"); - recommendation.setAttribute("id", "cfrMessageContainer"); + headerContainer.classList.add(MARKUP_IDENTIFIERS.headerClass); + recommendation.setAttribute("id", MARKUP_IDENTIFIERS.messageContainer); recommendation.addEventListener("click", async e => { target.hidePopup(); const url = await FxAccounts.config.promiseEmailFirstURI("bookmark"); @@ -157,25 +168,25 @@ class _BookmarkPanelHub { close.style.color = message.color; close.addEventListener("click", e => { this.sendUserEventTelemetry("DISMISS", win); - this.collapseMessage(); + this.collapseMessage(target); target.close(e); }); const title = createElement("h1"); - title.setAttribute("id", "editBookmarkPanelRecommendationTitle"); + title.setAttribute("id", MARKUP_IDENTIFIERS.messageHeader); const content = createElement("p"); - content.setAttribute("id", "editBookmarkPanelRecommendationContent"); + content.setAttribute("id", MARKUP_IDENTIFIERS.contentParagraph); const cta = createElement("button"); - cta.setAttribute("id", "editBookmarkPanelRecommendationCta"); + cta.setAttribute("id", MARKUP_IDENTIFIERS.ctaBtn); // If `string_id` is present it means we are relying on fluent for translations if (message.text.string_id) { - this._l10n.setAttributes( + document.l10n.setAttributes( close, message.close_button.tooltiptext.string_id ); - this._l10n.setAttributes(title, message.title.string_id); - this._l10n.setAttributes(content, message.text.string_id); - this._l10n.setAttributes(cta, message.cta.string_id); + document.l10n.setAttributes(title, message.title.string_id); + document.l10n.setAttributes(content, message.text.string_id); + document.l10n.setAttributes(cta, message.cta.string_id); } else { close.setAttribute("title", message.close_button.tooltiptext); title.textContent = message.title; @@ -191,7 +202,7 @@ class _BookmarkPanelHub { target.container.appendChild(recommendation); } - this.toggleRecommendation(true); + this.toggleRecommendation(target, true); this._adjustPanelHeight(win, recommendation); } @@ -229,12 +240,7 @@ class _BookmarkPanelHub { document.getElementById("editBookmarkPanelImage").style.height = ""; } - toggleRecommendation(visible) { - if (!this._response) { - return; - } - - const { target } = this._response; + toggleRecommendation(target, visible) { if (visible === undefined) { // When called from the info button of the bookmark panel target.infoButton.checked = !target.infoButton.checked; @@ -243,60 +249,68 @@ class _BookmarkPanelHub { } if (target.infoButton.checked) { // If it was ever collapsed we need to cancel the state - this._response.collapsed = false; + this._state.collapsed = false; target.container.removeAttribute("disabled"); } else { target.container.setAttribute("disabled", "disabled"); } } - collapseMessage() { - this._response.collapsed = true; - this.toggleRecommendation(false); + collapseMessage(target) { + this._state.collapsed = true; + this.toggleRecommendation(target, false); } - _removeContainer(target) { - if (target || (this._response && this._response.target)) { - const container = ( - target || this._response.target - ).container.querySelector("#cfrMessageContainer"); - if (container) { - this._restorePanelHeight(this._response.win); - container.remove(); - } + _removeContainer(target, win) { + const container = target.document.getElementById( + MARKUP_IDENTIFIERS.messageContainer + ); + if (container) { + this._restorePanelHeight(win); + container.remove(); } } - hideMessage(target) { - this._removeContainer(target); - this.toggleRecommendation(false); - this._response = null; + removeMessage(target, win) { + this._removeContainer(target, win); + this.toggleRecommendation(target, false); + this._state = {}; } - _forceShowMessage(target, message) { - const doc = target.browser.ownerGlobal.gBrowser.ownerDocument; + async _forceShowMessage(target, message) { const win = target.browser.ownerGlobal.window; + // Bookmark the page to force the panel to show and + // remove the bookmark when the panel is hidden + win.StarUI.panel.addEventListener( + "popupshown", + () => { + win.StarUI._removeBookmarksOnPopupHidden = true; + }, + { once: true } + ); + await win.PlacesCommandHook.bookmarkPage(); + + const doc = target.browser.ownerGlobal.gBrowser.ownerDocument; const panelTarget = { - container: doc.getElementById("editBookmarkPanelRecommendation"), - infoButton: doc.getElementById("editBookmarkPanelInfoButton"), + container: doc.getElementById(MARKUP_IDENTIFIERS.panelAnchor), + infoButton: doc.getElementById(MARKUP_IDENTIFIERS.infoButton), document: doc, - close: e => { - e.stopPropagation(); - this.toggleRecommendation(false); - }, + }; + panelTarget.close = e => { + e.stopPropagation(); + this.toggleRecommendation(panelTarget, false); }; // Remove any existing message - this.hideMessage(panelTarget); + this.removeMessage(panelTarget, win); + // Reset the reference to the panel elements - this._response = { target: panelTarget, win }; - // Required if we want to preview messages that include fluent strings - win.MozXULElement.insertFTLIfNeeded("browser/newtab/asrouter.ftl"); - win.MozXULElement.insertFTLIfNeeded("browser/branding/sync-brand.ftl"); - this.showMessage(message.content, panelTarget, win); + this._state = { message, collapsed: false }; + this.insertFTLIfNeeded(win); + this.showMessage(panelTarget, win, message.content); } sendImpression() { - this._addImpression(this._response); + this._addImpression(this._state.message); } sendUserEventTelemetry(event, win) { @@ -307,8 +321,8 @@ class _BookmarkPanelHub { ) ) { this._sendTelemetry({ - message_id: this._response.id, - bucket_id: this._response.id, + message_id: this._state.message.id, + bucket_id: this._state.message.id, event, }); } diff --git a/test/browser/browser_asrouter_bookmarkpanel.js b/test/browser/browser_asrouter_bookmarkpanel.js index db89dcec07..ce3c21c26f 100644 --- a/test/browser/browser_asrouter_bookmarkpanel.js +++ b/test/browser/browser_asrouter_bookmarkpanel.js @@ -26,7 +26,6 @@ add_task(async function test_fxa_message_shown() { const [msg] = PanelTestProvider.getMessages(); const response = BookmarkPanelHub.onResponse( - msg, { container: document.getElementById("editBookmarkPanelRecommendation"), infoButton: document.getElementById("editBookmarkPanelInfoButton"), @@ -36,7 +35,8 @@ add_task(async function test_fxa_message_shown() { url: testURL, document, }, - window + window, + msg ); Assert.ok(response, "We sent a valid message"); diff --git a/test/unit/lib/BookmarkPanelHub.test.js b/test/unit/lib/BookmarkPanelHub.test.js index b5707c8632..1ff5babf2c 100644 --- a/test/unit/lib/BookmarkPanelHub.test.js +++ b/test/unit/lib/BookmarkPanelHub.test.js @@ -49,6 +49,7 @@ describe("BookmarkPanelHub", () => { classList: { add: sandbox.stub() }, appendChild: sandbox.stub(), querySelector: sandbox.stub(), + remove: sandbox.stub(), children: [], style: {}, getBoundingClientRect: sandbox.stub(), @@ -66,6 +67,13 @@ describe("BookmarkPanelHub", () => { MozXULElement: { insertFTLIfNeeded: sandbox.stub() }, document, requestAnimationFrame: x => x(), + StarUI: { + panel: { + addEventListener: sandbox.stub(), + }, + _removeBookmarksOnPopupHidden: false, + }, + PlacesCommandHook: { bookmarkPage: sandbox.stub().resolves() }, }; fakeTarget = { document, @@ -108,7 +116,6 @@ describe("BookmarkPanelHub", () => { assert.equal(instance._addImpression, fakeAddImpression); assert.equal(instance._handleMessageRequest, fakeHandleMessageRequest); assert.equal(instance._dispatch, fakeDispatch); - assert.ok(instance._l10n); assert.isTrue(instance._initialized); }); it("should return early if not initialized", async () => { @@ -123,7 +130,7 @@ describe("BookmarkPanelHub", () => { sandbox.restore(); }); it("should not re-request messages for the same URL", async () => { - instance._response = { url: "foo.com", content: true }; + instance._state = { url: "foo.com", message: { content: true } }; fakeTarget.url = "foo.com"; sandbox.stub(instance, "showMessage"); @@ -135,7 +142,7 @@ describe("BookmarkPanelHub", () => { it("should call handleMessageRequest", async () => { fakeHandleMessageRequest.resolves(fakeMessage); - await instance.messageRequest(fakeTarget, {}); + await instance.messageRequest(fakeTarget, fakeWindow); assert.calledOnce(fakeHandleMessageRequest); assert.calledWithExactly(fakeHandleMessageRequest, { @@ -145,14 +152,14 @@ describe("BookmarkPanelHub", () => { it("should call onResponse", async () => { fakeHandleMessageRequest.resolves(fakeMessage); - await instance.messageRequest(fakeTarget, {}); + await instance.messageRequest(fakeTarget, fakeWindow); assert.calledOnce(instance.onResponse); assert.calledWithExactly( instance.onResponse, - fakeMessage, fakeTarget, - {} + fakeWindow, + fakeMessage ); }); }); @@ -161,23 +168,23 @@ describe("BookmarkPanelHub", () => { instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch); sandbox.stub(instance, "showMessage"); sandbox.stub(instance, "sendImpression"); - sandbox.stub(instance, "hideMessage"); + sandbox.stub(instance, "removeMessage"); fakeTarget = { infoButton: { disabled: true } }; }); it("should show a message when called with a response", () => { - instance.onResponse({ content: "content" }, fakeTarget, fakeWindow); + instance.onResponse(fakeTarget, fakeWindow, { content: "content" }); assert.calledOnce(instance.showMessage); assert.calledWithExactly( instance.showMessage, - "content", fakeTarget, - fakeWindow + fakeWindow, + "content" ); assert.calledOnce(instance.sendImpression); }); it("should insert the appropriate ftl files with translations", () => { - instance.onResponse({ content: "content" }, fakeTarget, fakeWindow); + instance.onResponse(fakeTarget, fakeWindow, { content: "content" }); assert.calledTwice(fakeWindow.MozXULElement.insertFTLIfNeeded); assert.calledWith( @@ -192,7 +199,7 @@ describe("BookmarkPanelHub", () => { it("should dispatch a user impression", () => { sandbox.spy(instance, "sendUserEventTelemetry"); - instance.onResponse({ content: "content" }, fakeTarget, fakeWindow); + instance.onResponse(fakeTarget, fakeWindow, { content: "content" }); assert.calledOnce(instance.sendUserEventTelemetry); assert.calledWithExactly( @@ -211,7 +218,7 @@ describe("BookmarkPanelHub", () => { isBrowserPrivateStub.returns(true); sandbox.spy(instance, "sendUserEventTelemetry"); - instance.onResponse({ content: "content" }, fakeTarget, fakeWindow); + instance.onResponse(fakeTarget, fakeWindow, { content: "content" }); assert.calledOnce(instance.sendUserEventTelemetry); assert.calledWithExactly( @@ -222,39 +229,41 @@ describe("BookmarkPanelHub", () => { assert.notCalled(fakeDispatch); }); it("should hide existing messages if no response is provided", () => { - instance.onResponse(null, fakeTarget); + instance.onResponse(fakeTarget, fakeWindow, null); - assert.calledOnce(instance.hideMessage); - assert.calledWithExactly(instance.hideMessage, fakeTarget); + assert.calledOnce(instance.removeMessage); + assert.calledWithExactly(instance.removeMessage, fakeTarget, fakeWindow); }); }); describe("#showMessage.collapsed=false", () => { beforeEach(() => { instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch); sandbox.stub(instance, "toggleRecommendation"); - sandbox.stub(instance, "_response").value({ collapsed: false }); + sandbox + .stub(instance, "_state") + .value({ collapsed: false, message: fakeMessage }); }); it("should create a container", () => { - fakeTarget.container.querySelector.returns(false); + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessage, fakeTarget); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); assert.equal(fakeTarget.document.createElementNS.callCount, 6); assert.calledOnce(fakeTarget.container.appendChild); assert.notCalled(fakeL10n.setAttributes); }); it("should create a container (fluent message)", () => { - fakeTarget.container.querySelector.returns(false); + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessageFluent, fakeTarget); + instance.showMessage(fakeTarget, fakeWindow, fakeMessageFluent); assert.equal(fakeTarget.document.createElementNS.callCount, 6); assert.calledOnce(fakeTarget.container.appendChild); }); it("should set l10n attributes", () => { - fakeTarget.container.querySelector.returns(false); + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessageFluent, fakeTarget); + instance.showMessage(fakeTarget, fakeWindow, fakeMessageFluent); assert.equal(fakeL10n.setAttributes.callCount, 4); }); @@ -272,16 +281,14 @@ describe("BookmarkPanelHub", () => { ); }); it("should reuse the container", () => { - fakeTarget.container.querySelector.returns(true); - - instance.showMessage(fakeMessage, fakeTarget); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); assert.notCalled(fakeTarget.container.appendChild); }); it("should open a tab with FxA signup", async () => { - fakeTarget.container.querySelector.returns(false); + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessage, fakeTarget, fakeWindow); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); // Call the event listener cb await fakeContainer.addEventListener.firstCall.args[1](); @@ -289,9 +296,9 @@ describe("BookmarkPanelHub", () => { }); it("should send a click event", async () => { sandbox.stub(instance, "sendUserEventTelemetry"); - fakeTarget.container.querySelector.returns(false); + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessage, fakeTarget, fakeWindow); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); // Call the event listener cb await fakeContainer.addEventListener.firstCall.args[1](); @@ -304,9 +311,9 @@ describe("BookmarkPanelHub", () => { }); it("should send a click event", async () => { sandbox.stub(instance, "sendUserEventTelemetry"); - fakeTarget.container.querySelector.returns(false); + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessage, fakeTarget, fakeWindow); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); // Call the event listener cb await fakeContainer.addEventListener.firstCall.args[1](); @@ -318,11 +325,11 @@ describe("BookmarkPanelHub", () => { ); }); it("should collapse the message", () => { - fakeTarget.container.querySelector.returns(false); sandbox.spy(instance, "collapseMessage"); - instance._response.collapsed = false; + instance._state.collapsed = false; + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessage, fakeTarget, fakeWindow); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); // Show message calls it once so we need to reset instance.toggleRecommendation.reset(); // Call the event listener cb @@ -330,15 +337,16 @@ describe("BookmarkPanelHub", () => { assert.calledOnce(instance.collapseMessage); assert.calledOnce(fakeTarget.close); - assert.isTrue(instance._response.collapsed); + assert.isTrue(instance._state.collapsed); assert.calledOnce(instance.toggleRecommendation); }); it("should send a dismiss event", () => { sandbox.stub(instance, "sendUserEventTelemetry"); sandbox.spy(instance, "collapseMessage"); - instance._response.collapsed = false; + instance._state.collapsed = false; + fakeWindow.document.getElementById.returns(null); - instance.showMessage(fakeMessage, fakeTarget, fakeWindow); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); // Call the event listener cb fakeContainer.addEventListener.secondCall.args[1](); @@ -350,124 +358,124 @@ describe("BookmarkPanelHub", () => { ); }); it("should call toggleRecommendation `true`", () => { - instance.showMessage(fakeMessage, fakeTarget, fakeWindow); + instance.showMessage(fakeTarget, fakeWindow, fakeMessage); assert.calledOnce(instance.toggleRecommendation); - assert.calledWithExactly(instance.toggleRecommendation, true); + assert.calledWithExactly(instance.toggleRecommendation, fakeTarget, true); }); }); describe("#showMessage.collapsed=true", () => { beforeEach(() => { - sandbox - .stub(instance, "_response") - .value({ collapsed: true, target: fakeTarget }); + instance.init({}); + instance._state.collapsed = true; sandbox.stub(instance, "toggleRecommendation"); }); it("should return early if the message is collapsed", () => { - instance.showMessage(); + instance.showMessage(fakeTarget, fakeWindow); assert.calledOnce(instance.toggleRecommendation); - assert.calledWithExactly(instance.toggleRecommendation, false); + assert.calledWithExactly( + instance.toggleRecommendation, + fakeTarget, + false + ); }); }); - describe("#hideMessage", () => { - let removeStub; + describe("#removeMessage", () => { beforeEach(() => { sandbox.stub(instance, "toggleRecommendation"); - removeStub = sandbox.stub(); - fakeTarget = { - container: { - querySelector: sandbox.stub().returns({ remove: removeStub }), - }, - }; - instance._response = { win: fakeWindow }; }); it("should remove the message", () => { - instance.hideMessage(fakeTarget); + instance.removeMessage(fakeTarget, fakeWindow); - assert.calledOnce(removeStub); + assert.calledOnce(fakeContainer.remove); }); it("should call toggleRecommendation `false`", () => { - instance.hideMessage(fakeTarget); + instance.removeMessage(fakeTarget, fakeWindow); assert.calledOnce(instance.toggleRecommendation); - assert.calledWithExactly(instance.toggleRecommendation, false); + assert.calledWithExactly( + instance.toggleRecommendation, + fakeTarget, + false + ); }); }); describe("#toggleRecommendation", () => { - let target; beforeEach(() => { - target = { - infoButton: {}, - container: { - setAttribute: sandbox.stub(), - removeAttribute: sandbox.stub(), - }, - }; - sandbox.stub(instance, "_response").value({ target }); + instance._state = {}; }); it("should check infoButton", () => { - instance.toggleRecommendation(true); + instance.toggleRecommendation(fakeTarget, true); - assert.isTrue(target.infoButton.checked); + assert.isTrue(fakeTarget.infoButton.checked); }); it("should uncheck infoButton", () => { - instance.toggleRecommendation(false); + instance.toggleRecommendation(fakeTarget, false); - assert.isFalse(target.infoButton.checked); + assert.isFalse(fakeTarget.infoButton.checked); }); it("should uncheck infoButton", () => { - target.infoButton.checked = true; + fakeTarget.infoButton.checked = true; - instance.toggleRecommendation(); + instance.toggleRecommendation(fakeTarget); - assert.isFalse(target.infoButton.checked); + assert.isFalse(fakeTarget.infoButton.checked); }); it("should disable the container", () => { - target.infoButton.checked = true; + fakeTarget.infoButton.checked = true; - instance.toggleRecommendation(); + instance.toggleRecommendation(fakeTarget); - assert.calledOnce(target.container.setAttribute); + assert.calledOnce(fakeTarget.container.setAttribute); }); it("should enable container", () => { - target.infoButton.checked = false; + fakeTarget.infoButton.checked = false; - instance.toggleRecommendation(); + instance.toggleRecommendation(fakeTarget); - assert.calledOnce(target.container.removeAttribute); + assert.calledOnce(fakeTarget.container.removeAttribute); + assert.isFalse(instance._state.collapsed); }); }); describe("#_forceShowMessage", () => { - it("should call showMessage with the correct args", () => { + it("should call showMessage with the correct args", async () => { sandbox.spy(instance, "showMessage"); - sandbox.stub(instance, "hideMessage"); + sandbox.stub(instance, "removeMessage"); - instance._forceShowMessage(fakeTarget, { content: fakeMessage }); + await instance._forceShowMessage(fakeTarget, { content: fakeMessage }); assert.calledOnce(instance.showMessage); - assert.calledOnce(instance.hideMessage); + assert.calledOnce(instance.removeMessage); assert.calledWithExactly( instance.showMessage, - fakeMessage, sinon.match.object, - fakeWindow + fakeWindow, + fakeMessage ); }); - it("should insert required fluent files", () => { + it("should insert required fluent files", async () => { sandbox.stub(instance, "showMessage"); - instance._forceShowMessage(fakeTarget, { content: fakeMessage }); + await instance._forceShowMessage(fakeTarget, { content: fakeMessage }); assert.calledTwice(fakeWindow.MozXULElement.insertFTLIfNeeded); }); - it("should insert a message you can collapse", () => { + it("should insert a message you can collapse", async () => { sandbox.spy(instance, "showMessage"); sandbox.stub(instance, "toggleRecommendation"); sandbox.stub(instance, "sendUserEventTelemetry"); + // Force rendering the message + fakeWindow.document.getElementById + .returns(null) + .withArgs("editBookmarkPanelRecommendation") + .returns(fakeContainer); + + await instance._forceShowMessage(fakeTarget, { content: fakeMessage }); - instance._forceShowMessage(fakeTarget, { content: fakeMessage }); + assert.calledTwice(fakeContainer.addEventListener); + const [target] = instance.showMessage.firstCall.args; const [ , eventListenerCb, @@ -476,13 +484,29 @@ describe("BookmarkPanelHub", () => { instance.toggleRecommendation.reset(); eventListenerCb({ stopPropagation: sandbox.stub() }); - assert.calledWithExactly(instance.toggleRecommendation, false); + assert.calledWithExactly(instance.toggleRecommendation, target, false); + }); + it("should open the bookmark panel when force showing the message", async () => { + sandbox.stub(instance, "showMessage"); + sandbox.stub(instance, "removeMessage"); + + await instance._forceShowMessage(fakeTarget, { content: fakeMessage }); + const [ + , + popupshownCb, + ] = fakeWindow.StarUI.panel.addEventListener.firstCall.args; + + assert.isFalse(fakeWindow.StarUI._removeBookmarksOnPopupHidden); + // call the event listener + popupshownCb(); + + assert.isTrue(fakeWindow.StarUI._removeBookmarksOnPopupHidden); }); }); describe("#sendImpression", () => { beforeEach(() => { instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch); - instance._response = "foo"; + instance._state = { message: "foo" }; }); it("should dispatch an impression", () => { instance.sendImpression();