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/ToolbarPanelHub.test.js | 934 +++++++++++++++++++++ 1 file changed, 934 insertions(+) create mode 100644 browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js (limited to 'browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js') diff --git a/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js b/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js new file mode 100644 index 0000000000..36fcc0cbe3 --- /dev/null +++ b/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js @@ -0,0 +1,934 @@ +import { _ToolbarPanelHub } from "lib/ToolbarPanelHub.jsm"; +import { GlobalOverrider } from "test/unit/utils"; +import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm"; +import { PanelTestProvider } from "lib/PanelTestProvider.sys.mjs"; + +describe("ToolbarPanelHub", () => { + let globals; + let sandbox; + let instance; + let everyWindowStub; + let preferencesStub; + let fakeDocument; + let fakeWindow; + let fakeElementById; + let fakeElementByTagName; + let createdCustomElements = []; + let eventListeners = {}; + let addObserverStub; + let removeObserverStub; + let getBoolPrefStub; + let setBoolPrefStub; + let waitForInitializedStub; + let isBrowserPrivateStub; + let fakeSendTelemetry; + let getEarliestRecordedDateStub; + let getEventsByDateRangeStub; + let defaultSearchStub; + let scriptloaderStub; + let fakeRemoteL10n; + let getViewNodeStub; + + beforeEach(async () => { + sandbox = sinon.createSandbox(); + globals = new GlobalOverrider(); + instance = new _ToolbarPanelHub(); + waitForInitializedStub = sandbox.stub().resolves(); + fakeElementById = { + setAttribute: sandbox.stub(), + removeAttribute: sandbox.stub(), + querySelector: sandbox.stub().returns(null), + querySelectorAll: sandbox.stub().returns([]), + appendChild: sandbox.stub(), + addEventListener: sandbox.stub(), + hasAttribute: sandbox.stub(), + toggleAttribute: sandbox.stub(), + remove: sandbox.stub(), + removeChild: sandbox.stub(), + }; + fakeElementByTagName = { + setAttribute: sandbox.stub(), + removeAttribute: sandbox.stub(), + querySelector: sandbox.stub().returns(null), + querySelectorAll: sandbox.stub().returns([]), + appendChild: sandbox.stub(), + addEventListener: sandbox.stub(), + hasAttribute: sandbox.stub(), + toggleAttribute: sandbox.stub(), + remove: sandbox.stub(), + removeChild: sandbox.stub(), + }; + fakeDocument = { + getElementById: sandbox.stub().returns(fakeElementById), + getElementsByTagName: sandbox.stub().returns(fakeElementByTagName), + querySelector: sandbox.stub().returns({}), + createElement: tagName => { + const element = { + tagName, + classList: {}, + addEventListener: (ev, fn) => { + eventListeners[ev] = fn; + }, + appendChild: sandbox.stub(), + setAttribute: sandbox.stub(), + textContent: "", + }; + element.classList.add = sandbox.stub(); + element.classList.includes = className => + element.classList.add.firstCall.args[0] === className; + createdCustomElements.push(element); + return element; + }, + l10n: { + translateElements: sandbox.stub(), + translateFragment: sandbox.stub(), + formatMessages: sandbox.stub().resolves([{}]), + setAttributes: sandbox.stub(), + }, + }; + fakeWindow = { + // eslint-disable-next-line object-shorthand + DocumentFragment: function () { + return fakeElementById; + }, + document: fakeDocument, + browser: { + ownerDocument: fakeDocument, + }, + MozXULElement: { insertFTLIfNeeded: sandbox.stub() }, + ownerGlobal: { + openLinkIn: sandbox.stub(), + gBrowser: "gBrowser", + }, + PanelUI: { + panel: fakeElementById, + whatsNewPanel: fakeElementById, + }, + customElements: { get: sandbox.stub() }, + }; + everyWindowStub = { + registerCallback: sandbox.stub(), + unregisterCallback: sandbox.stub(), + }; + preferencesStub = { + get: sandbox.stub(), + set: sandbox.stub(), + }; + scriptloaderStub = { loadSubScript: sandbox.stub() }; + addObserverStub = sandbox.stub(); + removeObserverStub = sandbox.stub(); + getBoolPrefStub = sandbox.stub(); + setBoolPrefStub = sandbox.stub(); + fakeSendTelemetry = sandbox.stub(); + isBrowserPrivateStub = sandbox.stub(); + getEarliestRecordedDateStub = sandbox.stub().returns( + // A random date that's not the current timestamp + new Date() - 500 + ); + getEventsByDateRangeStub = sandbox.stub().returns([]); + getViewNodeStub = sandbox.stub().returns(fakeElementById); + defaultSearchStub = { defaultEngine: { name: "DDG" } }; + fakeRemoteL10n = { + l10n: {}, + reloadL10n: sandbox.stub(), + createElement: sandbox + .stub() + .callsFake((doc, el) => fakeDocument.createElement(el)), + }; + globals.set({ + EveryWindow: everyWindowStub, + Services: { + ...Services, + prefs: { + addObserver: addObserverStub, + removeObserver: removeObserverStub, + getBoolPref: getBoolPrefStub, + setBoolPref: setBoolPrefStub, + }, + search: defaultSearchStub, + scriptloader: scriptloaderStub, + }, + PrivateBrowsingUtils: { + isBrowserPrivate: isBrowserPrivateStub, + }, + Preferences: preferencesStub, + TrackingDBService: { + getEarliestRecordedDate: getEarliestRecordedDateStub, + getEventsByDateRange: getEventsByDateRangeStub, + }, + SpecialMessageActions: { + handleAction: sandbox.stub(), + }, + RemoteL10n: fakeRemoteL10n, + PanelMultiView: { + getViewNode: getViewNodeStub, + }, + }); + }); + afterEach(() => { + instance.uninit(); + sandbox.restore(); + globals.restore(); + eventListeners = {}; + createdCustomElements = []; + }); + it("should create an instance", () => { + assert.ok(instance); + }); + it("should enableAppmenuButton() on init() just once", async () => { + instance.enableAppmenuButton = sandbox.stub(); + + await instance.init(waitForInitializedStub, { getMessages: () => {} }); + await instance.init(waitForInitializedStub, { getMessages: () => {} }); + + assert.calledOnce(instance.enableAppmenuButton); + + instance.uninit(); + + await instance.init(waitForInitializedStub, { getMessages: () => {} }); + + assert.calledTwice(instance.enableAppmenuButton); + }); + it("should unregisterCallback on uninit()", () => { + instance.uninit(); + assert.calledTwice(everyWindowStub.unregisterCallback); + }); + describe("#maybeLoadCustomElement", () => { + it("should not load customElements a second time", () => { + instance.maybeLoadCustomElement({ customElements: new Map() }); + instance.maybeLoadCustomElement({ + customElements: new Map([["remote-text", true]]), + }); + + assert.calledOnce(scriptloaderStub.loadSubScript); + }); + }); + describe("#toggleWhatsNewPref", () => { + it("should call Preferences.set() with the opposite value", () => { + let checkbox = {}; + let event = { target: checkbox }; + // checkbox starts false + checkbox.checked = false; + + // toggling the checkbox to set the value to true; + // Preferences.set() gets called before the checkbox changes, + // so we have to call it with the opposite value. + instance.toggleWhatsNewPref(event); + + assert.calledOnce(preferencesStub.set); + assert.calledWith( + preferencesStub.set, + "browser.messaging-system.whatsNewPanel.enabled", + !checkbox.checked + ); + }); + it("should report telemetry with the opposite value", () => { + let sendUserEventTelemetryStub = sandbox.stub( + instance, + "sendUserEventTelemetry" + ); + let event = { + target: { checked: true, ownerGlobal: fakeWindow }, + }; + + instance.toggleWhatsNewPref(event); + + assert.calledOnce(sendUserEventTelemetryStub); + const { args } = sendUserEventTelemetryStub.firstCall; + assert.equal(args[1], "WNP_PREF_TOGGLE"); + assert.propertyVal(args[3].value, "prefValue", false); + }); + }); + describe("#enableAppmenuButton", () => { + it("should registerCallback on enableAppmenuButton() if there are messages", async () => { + await instance.init(waitForInitializedStub, { + getMessages: sandbox.stub().resolves([{}, {}]), + }); + // init calls `enableAppmenuButton` + everyWindowStub.registerCallback.resetHistory(); + + await instance.enableAppmenuButton(); + + assert.calledOnce(everyWindowStub.registerCallback); + assert.calledWithExactly( + everyWindowStub.registerCallback, + "appMenu-whatsnew-button", + sinon.match.func, + sinon.match.func + ); + }); + it("should not registerCallback on enableAppmenuButton() if there are no messages", async () => { + instance.init(waitForInitializedStub, { + getMessages: sandbox.stub().resolves([]), + }); + // init calls `enableAppmenuButton` + everyWindowStub.registerCallback.resetHistory(); + + await instance.enableAppmenuButton(); + + assert.notCalled(everyWindowStub.registerCallback); + }); + }); + describe("#disableAppmenuButton", () => { + it("should call the unregisterCallback", () => { + assert.notCalled(everyWindowStub.unregisterCallback); + + instance.disableAppmenuButton(); + + assert.calledOnce(everyWindowStub.unregisterCallback); + assert.calledWithExactly( + everyWindowStub.unregisterCallback, + "appMenu-whatsnew-button" + ); + }); + }); + describe("#enableToolbarButton", () => { + it("should registerCallback on enableToolbarButton if messages.length", async () => { + await instance.init(waitForInitializedStub, { + getMessages: sandbox.stub().resolves([{}, {}]), + }); + // init calls `enableAppmenuButton` + everyWindowStub.registerCallback.resetHistory(); + + await instance.enableToolbarButton(); + + assert.calledOnce(everyWindowStub.registerCallback); + assert.calledWithExactly( + everyWindowStub.registerCallback, + "whats-new-menu-button", + sinon.match.func, + sinon.match.func + ); + }); + it("should not registerCallback on enableToolbarButton if no messages", async () => { + await instance.init(waitForInitializedStub, { + getMessages: sandbox.stub().resolves([]), + }); + + await instance.enableToolbarButton(); + + assert.notCalled(everyWindowStub.registerCallback); + }); + }); + describe("Show/Hide functions", () => { + it("should unhide appmenu button on _showAppmenuButton()", async () => { + await instance._showAppmenuButton(fakeWindow); + + assert.equal(fakeElementById.hidden, false); + }); + it("should hide appmenu button on _hideAppmenuButton()", () => { + instance._hideAppmenuButton(fakeWindow); + assert.equal(fakeElementById.hidden, true); + }); + it("should not do anything if the window is closed", () => { + instance._hideAppmenuButton(fakeWindow, true); + assert.notCalled(global.PanelMultiView.getViewNode); + }); + it("should not throw if the element does not exist", () => { + let fn = instance._hideAppmenuButton.bind(null, { + browser: { ownerDocument: {} }, + }); + getViewNodeStub.returns(undefined); + assert.doesNotThrow(fn); + }); + it("should unhide toolbar button on _showToolbarButton()", async () => { + await instance._showToolbarButton(fakeWindow); + + assert.equal(fakeElementById.hidden, false); + }); + it("should hide toolbar button on _hideToolbarButton()", () => { + instance._hideToolbarButton(fakeWindow); + assert.equal(fakeElementById.hidden, true); + }); + }); + describe("#renderMessages", () => { + let getMessagesStub; + beforeEach(() => { + getMessagesStub = sandbox.stub(); + instance.init(waitForInitializedStub, { + getMessages: getMessagesStub, + sendTelemetry: fakeSendTelemetry, + }); + }); + it("should have correct state", async () => { + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + + getMessagesStub.returns(messages); + const ev1 = sandbox.stub(); + ev1.withArgs("type").returns(1); // tracker + ev1.withArgs("count").returns(4); + const ev2 = sandbox.stub(); + ev2.withArgs("type").returns(4); // fingerprinter + ev2.withArgs("count").returns(3); + getEventsByDateRangeStub.returns([ + { getResultByName: ev1 }, + { getResultByName: ev2 }, + ]); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.propertyVal(instance.state.contentArguments, "trackerCount", 4); + assert.propertyVal( + instance.state.contentArguments, + "fingerprinterCount", + 3 + ); + }); + it("should render messages to the panel on renderMessages()", async () => { + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + messages[0].content.link_text = { string_id: "link_text_id" }; + + getMessagesStub.returns(messages); + const ev1 = sandbox.stub(); + ev1.withArgs("type").returns(1); // tracker + ev1.withArgs("count").returns(4); + const ev2 = sandbox.stub(); + ev2.withArgs("type").returns(4); // fingerprinter + ev2.withArgs("count").returns(3); + getEventsByDateRangeStub.returns([ + { getResultByName: ev1 }, + { getResultByName: ev2 }, + ]); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + for (let message of messages) { + assert.ok( + fakeRemoteL10n.createElement.args.find( + ([doc, el, args]) => + args && args.classList === "whatsNew-message-title" + ) + ); + if (message.content.layout === "tracking-protections") { + assert.ok( + fakeRemoteL10n.createElement.args.find( + ([doc, el, args]) => + args && args.classList === "whatsNew-message-subtitle" + ) + ); + } + if (message.id === "WHATS_NEW_FINGERPRINTER_COUNTER_72") { + assert.ok( + fakeRemoteL10n.createElement.args.find( + ([doc, el, args]) => el === "h2" && args.content === 3 + ) + ); + } + assert.ok( + fakeRemoteL10n.createElement.args.find( + ([doc, el, args]) => + args && args.classList === "whatsNew-message-content" + ) + ); + } + // Call the click handler to make coverage happy. + eventListeners.mouseup(); + assert.calledOnce(global.SpecialMessageActions.handleAction); + }); + it("should clear previous messages on 2nd renderMessages()", async () => { + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + const removeStub = sandbox.stub(); + fakeElementById.querySelectorAll.onCall(0).returns([]); + fakeElementById.querySelectorAll + .onCall(1) + .returns([{ remove: removeStub }, { remove: removeStub }]); + + getMessagesStub.returns(messages); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.calledTwice(removeStub); + }); + it("should sort based on order field value", async () => { + const messages = (await PanelTestProvider.getMessages()).filter( + m => + m.template === "whatsnew_panel_message" && + m.content.published_date === 1560969794394 + ); + + messages.forEach(m => (m.content.title = m.order)); + + getMessagesStub.returns(messages); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + // Select the title elements that are supposed to be set to the same + // value as the `order` field of the message + const titleEls = fakeRemoteL10n.createElement.args + .filter( + ([doc, el, args]) => + args && args.classList === "whatsNew-message-title" + ) + .map(([doc, el, args]) => args.content); + assert.deepEqual(titleEls, [1, 2, 3]); + }); + it("should accept string for image attributes", async () => { + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.id === "WHATS_NEW_70_1" + ); + getMessagesStub.returns(messages); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + const imageEl = createdCustomElements.find(el => el.tagName === "img"); + assert.calledOnce(imageEl.setAttribute); + assert.calledWithExactly( + imageEl.setAttribute, + "alt", + "Firefox Send Logo" + ); + }); + it("should set state values as data-attribute", async () => { + const message = (await PanelTestProvider.getMessages()).find( + m => m.template === "whatsnew_panel_message" + ); + getMessagesStub.returns([message]); + instance.state.contentArguments = { foo: "foo", bar: "bar" }; + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + const [, , args] = fakeRemoteL10n.createElement.args.find( + ([doc, el, elArgs]) => elArgs && elArgs.attributes + ); + assert.ok(args); + // Currently this.state.contentArguments has 8 different entries + assert.lengthOf(Object.keys(args.attributes), 8); + assert.equal( + args.attributes.searchEngineName, + defaultSearchStub.defaultEngine.name + ); + }); + it("should only render unique dates (no duplicates)", async () => { + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + const uniqueDates = [ + ...new Set(messages.map(m => m.content.published_date)), + ]; + getMessagesStub.returns(messages); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + const dateElements = fakeRemoteL10n.createElement.args.filter( + ([doc, el, args]) => + el === "p" && args.classList === "whatsNew-message-date" + ); + assert.lengthOf(dateElements, uniqueDates.length); + }); + it("should listen for panelhidden and remove the toolbar button", async () => { + getMessagesStub.returns([]); + fakeDocument.getElementById + .withArgs("customizationui-widget-panel") + .returns(null); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.notCalled(fakeElementById.addEventListener); + }); + it("should attach doCommand cbs that handle user actions", async () => { + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + getMessagesStub.returns(messages); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + const messageEl = createdCustomElements.find( + el => + el.tagName === "div" && el.classList.includes("whatsNew-message-body") + ); + const anchorEl = createdCustomElements.find(el => el.tagName === "a"); + + assert.notCalled(global.SpecialMessageActions.handleAction); + + messageEl.doCommand(); + anchorEl.doCommand(); + + assert.calledTwice(global.SpecialMessageActions.handleAction); + }); + it("should listen for panelhidden and remove the toolbar button", async () => { + getMessagesStub.returns([]); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.calledOnce(fakeElementById.addEventListener); + assert.calledWithExactly( + fakeElementById.addEventListener, + "popuphidden", + sinon.match.func, + { + once: true, + } + ); + const [, cb] = fakeElementById.addEventListener.firstCall.args; + + assert.notCalled(everyWindowStub.unregisterCallback); + + cb(); + + assert.calledOnce(everyWindowStub.unregisterCallback); + assert.calledWithExactly( + everyWindowStub.unregisterCallback, + "whats-new-menu-button" + ); + }); + describe("#IMPRESSION", () => { + it("should dispatch a IMPRESSION for messages", async () => { + // means panel is triggered from the toolbar button + fakeElementById.hasAttribute.returns(true); + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + getMessagesStub.returns(messages); + const spy = sandbox.spy(instance, "sendUserEventTelemetry"); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.calledOnce(spy); + assert.calledOnce(fakeSendTelemetry); + assert.propertyVal( + spy.firstCall.args[2], + "id", + messages + .map(({ id }) => id) + .sort() + .join(",") + ); + }); + it("should dispatch a CLICK for clicking a message", async () => { + // means panel is triggered from the toolbar button + fakeElementById.hasAttribute.returns(true); + // Force to render the message + fakeElementById.querySelector.returns(null); + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + getMessagesStub.returns([messages[0]]); + const spy = sandbox.spy(instance, "sendUserEventTelemetry"); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.calledOnce(spy); + assert.calledOnce(fakeSendTelemetry); + + spy.resetHistory(); + + // Message click event listener cb + eventListeners.mouseup(); + + assert.calledOnce(spy); + assert.calledWithExactly(spy, fakeWindow, "CLICK", messages[0]); + }); + it("should dispatch a IMPRESSION with toolbar_dropdown", async () => { + // means panel is triggered from the toolbar button + fakeElementById.hasAttribute.returns(true); + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + getMessagesStub.resolves(messages); + const spy = sandbox.spy(instance, "sendUserEventTelemetry"); + const panelPingId = messages + .map(({ id }) => id) + .sort() + .join(","); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.calledOnce(spy); + assert.calledWithExactly( + spy, + fakeWindow, + "IMPRESSION", + { + id: panelPingId, + }, + { + value: { + view: "toolbar_dropdown", + }, + } + ); + assert.calledOnce(fakeSendTelemetry); + const { + args: [dispatchPayload], + } = fakeSendTelemetry.lastCall; + assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY"); + assert.propertyVal(dispatchPayload.data, "message_id", panelPingId); + assert.deepEqual(dispatchPayload.data.event_context, { + view: "toolbar_dropdown", + }); + }); + it("should dispatch a IMPRESSION with application_menu", async () => { + // means panel is triggered as a subview in the application menu + fakeElementById.hasAttribute.returns(false); + const messages = (await PanelTestProvider.getMessages()).filter( + m => m.template === "whatsnew_panel_message" + ); + getMessagesStub.resolves(messages); + const spy = sandbox.spy(instance, "sendUserEventTelemetry"); + const panelPingId = messages + .map(({ id }) => id) + .sort() + .join(","); + + await instance.renderMessages(fakeWindow, fakeDocument, "container-id"); + + assert.calledOnce(spy); + assert.calledWithExactly( + spy, + fakeWindow, + "IMPRESSION", + { + id: panelPingId, + }, + { + value: { + view: "application_menu", + }, + } + ); + assert.calledOnce(fakeSendTelemetry); + const { + args: [dispatchPayload], + } = fakeSendTelemetry.lastCall; + assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY"); + assert.propertyVal(dispatchPayload.data, "message_id", panelPingId); + assert.deepEqual(dispatchPayload.data.event_context, { + view: "application_menu", + }); + }); + }); + describe("#forceShowMessage", () => { + const panelSelector = "PanelUI-whatsNew-message-container"; + let removeMessagesSpy; + let renderMessagesStub; + let addEventListenerStub; + let messages; + let browser; + beforeEach(async () => { + messages = (await PanelTestProvider.getMessages()).find( + m => m.id === "WHATS_NEW_70_1" + ); + removeMessagesSpy = sandbox.spy(instance, "removeMessages"); + renderMessagesStub = sandbox.spy(instance, "renderMessages"); + addEventListenerStub = fakeElementById.addEventListener; + browser = { ownerGlobal: fakeWindow, ownerDocument: fakeDocument }; + fakeElementById.querySelectorAll.returns([fakeElementById]); + }); + it("should call removeMessages when forcing a message to show", () => { + instance.forceShowMessage(browser, messages); + + assert.calledWithExactly(removeMessagesSpy, fakeWindow, panelSelector); + }); + it("should call renderMessages when forcing a message to show", () => { + instance.forceShowMessage(browser, messages); + + assert.calledOnce(renderMessagesStub); + assert.calledWithExactly( + renderMessagesStub, + fakeWindow, + fakeDocument, + panelSelector, + { + force: true, + messages: Array.isArray(messages) ? messages : [messages], + } + ); + }); + it("should cleanup after the panel is hidden when forcing a message to show", () => { + instance.forceShowMessage(browser, messages); + + assert.calledOnce(addEventListenerStub); + assert.calledWithExactly( + addEventListenerStub, + "popuphidden", + sinon.match.func + ); + + const [, cb] = addEventListenerStub.firstCall.args; + // Reset the call count from the first `forceShowMessage` call + removeMessagesSpy.resetHistory(); + cb({ target: { ownerGlobal: fakeWindow } }); + + assert.calledOnce(removeMessagesSpy); + assert.calledWithExactly(removeMessagesSpy, fakeWindow, panelSelector); + }); + it("should exit gracefully if called before a browser exists", () => { + instance.forceShowMessage(null, messages); + assert.neverCalledWith(removeMessagesSpy, fakeWindow, panelSelector); + }); + }); + }); + describe("#insertProtectionPanelMessage", () => { + const fakeInsert = () => + instance.insertProtectionPanelMessage({ + target: { ownerGlobal: fakeWindow, ownerDocument: fakeDocument }, + }); + let getMessagesStub; + beforeEach(async () => { + const onboardingMsgs = + await OnboardingMessageProvider.getUntranslatedMessages(); + getMessagesStub = sandbox + .stub() + .resolves( + onboardingMsgs.find(msg => msg.template === "protections_panel") + ); + await instance.init(waitForInitializedStub, { + sendTelemetry: fakeSendTelemetry, + getMessages: getMessagesStub, + }); + }); + it("should remember it showed", async () => { + await fakeInsert(); + + assert.calledWithExactly( + setBoolPrefStub, + "browser.protections_panel.infoMessage.seen", + true + ); + }); + it("should toggle/expand when default collapsed/disabled", async () => { + fakeElementById.hasAttribute.returns(true); + + await fakeInsert(); + + assert.calledThrice(fakeElementById.toggleAttribute); + }); + it("should toggle again when popup hides", async () => { + fakeElementById.addEventListener.callsArg(1); + + await fakeInsert(); + + assert.callCount(fakeElementById.toggleAttribute, 6); + }); + it("should open link on click (separate link element)", async () => { + const sendTelemetryStub = sandbox.stub( + instance, + "sendUserEventTelemetry" + ); + const onboardingMsgs = + await OnboardingMessageProvider.getUntranslatedMessages(); + const msg = onboardingMsgs.find(m => m.template === "protections_panel"); + + await fakeInsert(); + + assert.calledOnce(sendTelemetryStub); + assert.calledWithExactly( + sendTelemetryStub, + fakeWindow, + "IMPRESSION", + msg + ); + + eventListeners.mouseup(); + + assert.calledOnce(global.SpecialMessageActions.handleAction); + assert.calledWithExactly( + global.SpecialMessageActions.handleAction, + { + type: "OPEN_URL", + data: { + args: sinon.match.string, + where: "tabshifted", + }, + }, + fakeWindow.browser + ); + }); + it("should format the url", async () => { + const stub = sandbox + .stub(global.Services.urlFormatter, "formatURL") + .returns("formattedURL"); + const onboardingMsgs = + await OnboardingMessageProvider.getUntranslatedMessages(); + const msg = onboardingMsgs.find(m => m.template === "protections_panel"); + + await fakeInsert(); + + eventListeners.mouseup(); + + assert.calledOnce(stub); + assert.calledWithExactly(stub, msg.content.cta_url); + assert.calledOnce(global.SpecialMessageActions.handleAction); + assert.calledWithExactly( + global.SpecialMessageActions.handleAction, + { + type: "OPEN_URL", + data: { + args: "formattedURL", + where: "tabshifted", + }, + }, + fakeWindow.browser + ); + }); + it("should report format url errors", async () => { + const stub = sandbox + .stub(global.Services.urlFormatter, "formatURL") + .throws(); + const onboardingMsgs = + await OnboardingMessageProvider.getUntranslatedMessages(); + const msg = onboardingMsgs.find(m => m.template === "protections_panel"); + sandbox.spy(global.console, "error"); + + await fakeInsert(); + + eventListeners.mouseup(); + + assert.calledOnce(stub); + assert.calledOnce(global.console.error); + assert.calledOnce(global.SpecialMessageActions.handleAction); + assert.calledWithExactly( + global.SpecialMessageActions.handleAction, + { + type: "OPEN_URL", + data: { + args: msg.content.cta_url, + where: "tabshifted", + }, + }, + fakeWindow.browser + ); + }); + it("should open link on click (directly attached to the message)", async () => { + const onboardingMsgs = + await OnboardingMessageProvider.getUntranslatedMessages(); + const msg = onboardingMsgs.find(m => m.template === "protections_panel"); + getMessagesStub.resolves({ + ...msg, + content: { ...msg.content, link_text: null }, + }); + await fakeInsert(); + + eventListeners.mouseup(); + + assert.calledOnce(global.SpecialMessageActions.handleAction); + assert.calledWithExactly( + global.SpecialMessageActions.handleAction, + { + type: "OPEN_URL", + data: { + args: sinon.match.string, + where: "tabshifted", + }, + }, + fakeWindow.browser + ); + }); + it("should handle user actions from mouseup and keyup", async () => { + await fakeInsert(); + + eventListeners.mouseup(); + eventListeners.keyup({ key: "Enter" }); + eventListeners.keyup({ key: " " }); + assert.calledThrice(global.SpecialMessageActions.handleAction); + }); + }); +}); -- cgit v1.2.3