123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825 |
- import {CFRPageActions, PageAction} from "lib/CFRPageActions.jsm";
- import {FAKE_RECOMMENDATION} from "./constants";
- import {GlobalOverrider} from "test/unit/utils";
- describe("CFRPageActions", () => {
- let sandbox;
- let clock;
- let fakeRecommendation;
- let fakeHost;
- let fakeBrowser;
- let dispatchStub;
- let globals;
- let containerElem;
- let elements;
- const elementIDs = [
- "urlbar",
- "contextual-feature-recommendation",
- "cfr-button",
- "cfr-label",
- "contextual-feature-recommendation-notification",
- "cfr-notification-header-label",
- "cfr-notification-header-link",
- "cfr-notification-header-image",
- "cfr-notification-author",
- "cfr-notification-footer",
- "cfr-notification-footer-text",
- "cfr-notification-footer-filled-stars",
- "cfr-notification-footer-empty-stars",
- "cfr-notification-footer-users",
- "cfr-notification-footer-spacer",
- "cfr-notification-footer-learn-more-link",
- "cfr-notification-footer-pintab-animation-container",
- "cfr-notification-footer-animation-button",
- "cfr-notification-footer-animation-label",
- ];
- const elementClassNames = [
- "popup-notification-body-container",
- ];
- beforeEach(() => {
- sandbox = sinon.createSandbox();
- clock = sandbox.useFakeTimers();
- fakeRecommendation = {...FAKE_RECOMMENDATION};
- fakeHost = "mozilla.org";
- fakeBrowser = {
- documentURI: {
- scheme: "https",
- host: fakeHost,
- },
- ownerGlobal: window,
- };
- dispatchStub = sandbox.stub();
- globals = new GlobalOverrider();
- globals.set({
- DOMLocalization: class {},
- promiseDocumentFlushed: sandbox.stub().callsFake(fn => Promise.resolve(fn())),
- PopupNotifications: {
- show: sandbox.stub(),
- remove: sandbox.stub(),
- },
- PrivateBrowsingUtils: {isWindowPrivate: sandbox.stub().returns(false)},
- gBrowser: {selectedBrowser: fakeBrowser},
- });
- document.createXULElement = document.createElement;
- elements = {};
- const [body] = document.getElementsByTagName("body");
- containerElem = document.createElement("div");
- body.appendChild(containerElem);
- for (const id of elementIDs) {
- const elem = document.createElement("div");
- elem.setAttribute("id", id);
- containerElem.appendChild(elem);
- elements[id] = elem;
- }
- for (const className of elementClassNames) {
- const elem = document.createElement("div");
- elem.setAttribute("class", className);
- containerElem.appendChild(elem);
- elements[className] = elem;
- }
- });
- afterEach(() => {
- CFRPageActions.clearRecommendations();
- containerElem.remove();
- sandbox.restore();
- globals.restore();
- });
- describe("PageAction", () => {
- let pageAction;
- let getStringsStub;
- beforeEach(() => {
- pageAction = new PageAction(window, dispatchStub);
- getStringsStub = sandbox.stub(pageAction, "getStrings").resolves("");
- });
- describe("#showAddressBarNotifier", () => {
- it("should un-hideAddressBarNotifier the element and set the right label value", async () => {
- const FAKE_NOTIFICATION_TEXT = "FAKE_NOTIFICATION_TEXT";
- getStringsStub.withArgs(fakeRecommendation.content.notification_text).resolves(FAKE_NOTIFICATION_TEXT);
- await pageAction.showAddressBarNotifier(fakeRecommendation);
- assert.isFalse(pageAction.container.hidden);
- assert.equal(pageAction.label.value, FAKE_NOTIFICATION_TEXT);
- });
- it("should wait for the document layout to flush", async () => {
- sandbox.spy(pageAction.label, "getClientRects");
- await pageAction.showAddressBarNotifier(fakeRecommendation);
- assert.calledOnce(global.promiseDocumentFlushed);
- assert.callOrder(global.promiseDocumentFlushed, pageAction.label.getClientRects);
- });
- it("should set the CSS variable --cfr-label-width correctly", async () => {
- await pageAction.showAddressBarNotifier(fakeRecommendation);
- const expectedWidth = pageAction.label.getClientRects()[0].width;
- assert.equal(pageAction.urlbar.style.getPropertyValue("--cfr-label-width"),
- `${expectedWidth}px`);
- });
- it("should cause an expansion, and dispatch an impression iff `expand` is true", async () => {
- sandbox.spy(pageAction, "_clearScheduledStateChanges");
- sandbox.spy(pageAction, "_expand");
- sandbox.spy(pageAction, "_dispatchImpression");
- await pageAction.showAddressBarNotifier(fakeRecommendation);
- assert.notCalled(pageAction._dispatchImpression);
- clock.tick(1001);
- assert.notEqual(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "expanded");
- await pageAction.showAddressBarNotifier(fakeRecommendation, true);
- assert.calledOnce(pageAction._clearScheduledStateChanges);
- clock.tick(1001);
- assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "expanded");
- assert.calledOnce(pageAction._dispatchImpression);
- assert.calledWith(pageAction._dispatchImpression, fakeRecommendation);
- });
- it("should send telemetry if `expand` is true and the id and bucket_id are provided", async () => {
- await pageAction.showAddressBarNotifier(fakeRecommendation, true);
- assert.calledWith(dispatchStub, {
- type: "DOORHANGER_TELEMETRY",
- data: {
- action: "cfr_user_event",
- source: "CFR",
- message_id: fakeRecommendation.id,
- bucket_id: fakeRecommendation.content.bucket_id,
- event: "IMPRESSION",
- },
- });
- });
- });
- describe("#hideAddressBarNotifier", () => {
- it("should hideAddressBarNotifier the container, cancel any state changes, and remove the state attribute", () => {
- sandbox.spy(pageAction, "_clearScheduledStateChanges");
- pageAction.hideAddressBarNotifier();
- assert.isTrue(pageAction.container.hidden);
- assert.calledOnce(pageAction._clearScheduledStateChanges);
- assert.isNull(pageAction.urlbar.getAttribute("cfr-recommendation-state"));
- });
- it("should remove the `currentNotification`", () => {
- const notification = {};
- pageAction.currentNotification = notification;
- pageAction.hideAddressBarNotifier();
- assert.calledWith(global.PopupNotifications.remove, notification);
- });
- });
- describe("#_expand", () => {
- beforeEach(() => {
- pageAction._clearScheduledStateChanges();
- pageAction.urlbar.removeAttribute("cfr-recommendation-state");
- });
- it("without a delay, should clear other state changes and set the state to 'expanded'", () => {
- sandbox.spy(pageAction, "_clearScheduledStateChanges");
- pageAction._expand();
- assert.calledOnce(pageAction._clearScheduledStateChanges);
- assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "expanded");
- });
- it("with a delay, should set the expanded state after the correct amount of time", () => {
- const delay = 1234;
- pageAction._expand(delay);
- // We expect that an expansion has been scheduled
- assert.lengthOf(pageAction.stateTransitionTimeoutIDs, 1);
- clock.tick(delay + 1);
- assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "expanded");
- });
- });
- describe("#_collapse", () => {
- beforeEach(() => {
- pageAction._clearScheduledStateChanges();
- pageAction.urlbar.removeAttribute("cfr-recommendation-state");
- });
- it("without a delay, should clear other state changes and set the state to collapsed only if it's already expanded", () => {
- sandbox.spy(pageAction, "_clearScheduledStateChanges");
- pageAction._collapse();
- assert.calledOnce(pageAction._clearScheduledStateChanges);
- assert.isNull(pageAction.urlbar.getAttribute("cfr-recommendation-state"));
- pageAction.urlbar.setAttribute("cfr-recommendation-state", "expanded");
- pageAction._collapse();
- assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "collapsed");
- });
- it("with a delay, should set the collapsed state after the correct amount of time", () => {
- const delay = 1234;
- pageAction._collapse(delay);
- clock.tick(delay + 1);
- // The state was _not_ "expanded" and so should not have been set to "collapsed"
- assert.isNull(pageAction.urlbar.getAttribute("cfr-recommendation-state"));
- pageAction._expand();
- pageAction._collapse(delay);
- // We expect that a collapse has been scheduled
- assert.lengthOf(pageAction.stateTransitionTimeoutIDs, 1);
- clock.tick(delay + 1);
- // This time it was "expanded" so should now (after the delay) be "collapsed"
- assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "collapsed");
- });
- });
- describe("#_clearScheduledStateChanges", () => {
- it("should call .clearTimeout on all stored timeoutIDs", () => {
- pageAction.stateTransitionTimeoutIDs = [42, 73, 1997];
- sandbox.spy(pageAction.window, "clearTimeout");
- pageAction._clearScheduledStateChanges();
- assert.calledThrice(pageAction.window.clearTimeout);
- assert.calledWith(pageAction.window.clearTimeout, 42);
- assert.calledWith(pageAction.window.clearTimeout, 73);
- assert.calledWith(pageAction.window.clearTimeout, 1997);
- });
- });
- describe("#_popupStateChange", () => {
- it("should collapse and remove the notification on 'dismissed'", () => {
- pageAction._expand();
- const fakeNotification = {};
- pageAction.currentNotification = fakeNotification;
- pageAction._popupStateChange("dismissed");
- assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "collapsed");
- assert.calledOnce(global.PopupNotifications.remove);
- assert.calledWith(global.PopupNotifications.remove, fakeNotification);
- });
- it("should collapse and remove the notification on 'removed'", () => {
- pageAction._expand();
- const fakeNotification = {};
- pageAction.currentNotification = fakeNotification;
- pageAction._popupStateChange("removed");
- assert.equal(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "collapsed");
- assert.calledOnce(global.PopupNotifications.remove);
- assert.calledWith(global.PopupNotifications.remove, fakeNotification);
- });
- it("should do nothing for other states", () => {
- pageAction._popupStateChange("opened");
- assert.notCalled(global.PopupNotifications.remove);
- });
- });
- describe("#dispatchUserAction", () => {
- it("should call ._dispatchToASRouter with the right action", () => {
- const fakeAction = {};
- pageAction.dispatchUserAction(fakeAction);
- assert.calledOnce(dispatchStub);
- assert.calledWith(
- dispatchStub,
- {type: "USER_ACTION", data: fakeAction},
- {browser: fakeBrowser}
- );
- });
- });
- describe("#_dispatchImpression", () => {
- it("should call ._dispatchToASRouter with the right action", () => {
- pageAction._dispatchImpression("fake impression");
- assert.calledWith(dispatchStub, {type: "IMPRESSION", data: "fake impression"});
- });
- });
- describe("#_sendTelemetry", () => {
- it("should call ._dispatchToASRouter with the right action", () => {
- const fakePing = {message_id: 42};
- pageAction._sendTelemetry(fakePing);
- assert.calledWith(dispatchStub, {
- type: "DOORHANGER_TELEMETRY",
- data: {action: "cfr_user_event", source: "CFR", message_id: 42},
- });
- });
- });
- describe("#_blockMessage", () => {
- it("should call ._dispatchToASRouter with the right action", () => {
- pageAction._blockMessage("fake id");
- assert.calledOnce(dispatchStub);
- assert.calledWith(dispatchStub, {
- type: "BLOCK_MESSAGE_BY_ID",
- data: {id: "fake id"},
- });
- });
- });
- describe("#getStrings", () => {
- let formatMessagesStub;
- const localeStrings = [{
- value: "你好世界",
- attributes: [
- {name: "first_attr", value: 42},
- {name: "second_attr", value: "some string"},
- {name: "third_attr", value: [1, 2, 3]},
- ],
- }];
- beforeEach(() => {
- getStringsStub.restore();
- formatMessagesStub = sandbox.stub()
- .withArgs({id: "hello_world"})
- .resolves(localeStrings);
- global.DOMLocalization.prototype.formatMessages = formatMessagesStub;
- });
- it("should return the argument if a string_id is not defined", async () => {
- assert.deepEqual(await pageAction.getStrings({}), {});
- assert.equal(await pageAction.getStrings("some string"), "some string");
- });
- it("should get the right locale string", async () => {
- assert.equal(await pageAction.getStrings({string_id: "hello_world"}), localeStrings[0].value);
- });
- it("should return the right sub-attribute if specified", async () => {
- assert.equal(await pageAction.getStrings({string_id: "hello_world"}, "second_attr"), "some string");
- });
- it("should attach attributes to string overrides", async () => {
- const fromJson = {value: "Add Now", attributes: {accesskey: "A"}};
- const result = await pageAction.getStrings(fromJson);
- assert.equal(result, fromJson.value);
- assert.propertyVal(result.attributes, "accesskey", "A");
- });
- it("should return subAttributes when doing string overrides", async () => {
- const fromJson = {value: "Add Now", attributes: {accesskey: "A"}};
- const result = await pageAction.getStrings(fromJson, "accesskey");
- assert.equal(result, "A");
- });
- it("should resolve ftl strings and attach subAttributes", async () => {
- const fromFtl = {string_id: "cfr-doorhanger-extension-ok-button"};
- formatMessagesStub.resolves([{value: "Add Now", attributes: [{name: "accesskey", value: "A"}]}]);
- const result = await pageAction.getStrings(fromFtl);
- assert.equal(result, "Add Now");
- assert.propertyVal(result.attributes, "accesskey", "A");
- });
- it("should return subAttributes from ftl ids", async () => {
- const fromFtl = {string_id: "cfr-doorhanger-extension-ok-button"};
- formatMessagesStub.resolves([{value: "Add Now", attributes: [{name: "accesskey", value: "A"}]}]);
- const result = await pageAction.getStrings(fromFtl, "accesskey");
- assert.equal(result, "A");
- });
- it("should report an error when no attributes are present but subAttribute is requested", async () => {
- const fromJson = {value: "Foo"};
- const stub = sandbox.stub(global.Cu, "reportError");
- await pageAction.getStrings(fromJson, "accesskey");
- assert.calledOnce(stub);
- stub.restore();
- });
- });
- describe("#_showPopupOnClick", () => {
- let translateElementsStub;
- let setAttributesStub;
- beforeEach(async () => {
- CFRPageActions.PageActionMap.set(fakeBrowser.ownerGlobal, pageAction);
- await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
- getStringsStub.callsFake(async a => a) // eslint-disable-line max-nested-callbacks
- .withArgs({string_id: "primary_button_id"})
- .resolves({value: "Primary Button", attributes: {accesskey: "p"}})
- .withArgs({string_id: "secondary_button_id"})
- .resolves({value: "Secondary Button", attributes: {accesskey: "s"}})
- .withArgs({string_id: "secondary_button_id_2"})
- .resolves({value: "Secondary Button 2", attributes: {accesskey: "a"}})
- .withArgs({string_id: "secondary_button_id_3"})
- .resolves({value: "Secondary Button 3", attributes: {accesskey: "g"}})
- .withArgs(sinon.match({string_id: "cfr-doorhanger-extension-learn-more-link"}))
- .resolves("Learn more")
- .withArgs(sinon.match({string_id: "cfr-doorhanger-extension-total-users"}))
- .callsFake(async ({args}) => `${args.total} users`); // eslint-disable-line max-nested-callbacks
- translateElementsStub = sandbox.stub().resolves();
- setAttributesStub = sandbox.stub();
- global.DOMLocalization.prototype.setAttributes = setAttributesStub;
- global.DOMLocalization.prototype.translateElements = translateElementsStub;
- });
- it("should call `.hideAddressBarNotifier` and do nothing if there is no recommendation for the selected browser", async () => {
- sandbox.spy(pageAction, "hideAddressBarNotifier");
- CFRPageActions.RecommendationMap.delete(fakeBrowser);
- await pageAction._showPopupOnClick({});
- assert.calledOnce(pageAction.hideAddressBarNotifier);
- assert.notCalled(global.PopupNotifications.show);
- });
- it("should cancel any planned state changes", async () => {
- sandbox.spy(pageAction, "_clearScheduledStateChanges");
- assert.notCalled(pageAction._clearScheduledStateChanges);
- await pageAction._showPopupOnClick({});
- assert.calledOnce(pageAction._clearScheduledStateChanges);
- });
- it("should set the right text values", async () => {
- await pageAction._showPopupOnClick({});
- const headerLabel = elements["cfr-notification-header-label"];
- const headerLink = elements["cfr-notification-header-link"];
- const headerImage = elements["cfr-notification-header-image"];
- const footerText = elements["cfr-notification-footer-text"];
- const footerLink = elements["cfr-notification-footer-learn-more-link"];
- assert.equal(headerLabel.value, fakeRecommendation.content.heading_text);
- assert.isTrue(headerLink.getAttribute("href").endsWith(fakeRecommendation.content.info_icon.sumo_path));
- assert.equal(headerImage.getAttribute("tooltiptext"), fakeRecommendation.content.info_icon.label);
- assert.equal(footerText.textContent, fakeRecommendation.content.text);
- assert.equal(footerLink.value, "Learn more");
- assert.equal(footerLink.getAttribute("href"), fakeRecommendation.content.addon.amo_url);
- });
- it("should add the rating correctly", async () => {
- await pageAction._showPopupOnClick();
- const footerFilledStars = elements["cfr-notification-footer-filled-stars"];
- const footerEmptyStars = elements["cfr-notification-footer-empty-stars"];
- // .toFixed to sort out some floating precision errors
- assert.equal(footerFilledStars.style.width, `${(4.2 * 17).toFixed(1)}px`);
- assert.equal(footerEmptyStars.style.width, `${(0.8 * 17).toFixed(1)}px`);
- });
- it("should add the number of users correctly", async () => {
- await pageAction._showPopupOnClick();
- const footerUsers = elements["cfr-notification-footer-users"];
- assert.isNull(footerUsers.getAttribute("hidden"));
- assert.equal(footerUsers.getAttribute("value"), `${fakeRecommendation.content.addon.users} users`);
- });
- it("should send the right telemetry", async () => {
- await pageAction._showPopupOnClick();
- assert.calledWith(dispatchStub, {
- type: "DOORHANGER_TELEMETRY",
- data: {
- action: "cfr_user_event",
- source: "CFR",
- message_id: fakeRecommendation.id,
- bucket_id: fakeRecommendation.content.bucket_id,
- event: "CLICK_DOORHANGER",
- },
- });
- });
- it("should set the main action correctly", async () => {
- sinon.stub(CFRPageActions, "_fetchLatestAddonVersion").resolves("latest-addon.xpi");
- await pageAction._showPopupOnClick();
- const mainAction = global.PopupNotifications.show.firstCall.args[4]; // eslint-disable-line prefer-destructuring
- assert.deepEqual(mainAction.label, {value: "Primary Button", attributes: {accesskey: "p"}});
- sandbox.spy(pageAction, "hideAddressBarNotifier");
- await mainAction.callback();
- assert.calledOnce(pageAction.hideAddressBarNotifier);
- // Should block the message
- assert.calledWith(dispatchStub, {
- type: "BLOCK_MESSAGE_BY_ID",
- data: {id: fakeRecommendation.id},
- });
- // Should trigger the action
- assert.calledWith(
- dispatchStub,
- {type: "USER_ACTION", data: {id: "primary_action", data: {url: "latest-addon.xpi"}}},
- {browser: fakeBrowser}
- );
- // Should send telemetry
- assert.calledWith(dispatchStub, {
- type: "DOORHANGER_TELEMETRY",
- data: {
- action: "cfr_user_event",
- source: "CFR",
- message_id: fakeRecommendation.id,
- bucket_id: fakeRecommendation.content.bucket_id,
- event: "INSTALL",
- },
- });
- // Should remove the recommendation
- assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
- });
- it("should set the secondary action correctly", async () => {
- await pageAction._showPopupOnClick();
- const [secondaryAction] = global.PopupNotifications.show.firstCall.args[5]; // eslint-disable-line prefer-destructuring
- assert.deepEqual(secondaryAction.label, {value: "Secondary Button", attributes: {accesskey: "s"}});
- sandbox.spy(pageAction, "hideAddressBarNotifier");
- CFRPageActions.RecommendationMap.set(fakeBrowser, {});
- secondaryAction.callback();
- // Should send telemetry
- assert.calledWith(dispatchStub, {
- type: "DOORHANGER_TELEMETRY",
- data: {
- action: "cfr_user_event",
- source: "CFR",
- message_id: fakeRecommendation.id,
- bucket_id: fakeRecommendation.content.bucket_id,
- event: "DISMISS",
- },
- });
- // Don't remove the recommendation on `DISMISS` action
- assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
- assert.notCalled(pageAction.hideAddressBarNotifier);
- });
- it("should send right telemetry for BLOCK secondary action", async () => {
- await pageAction._showPopupOnClick();
- const blockAction = global.PopupNotifications.show.firstCall.args[5][1]; // eslint-disable-line prefer-destructuring
- assert.deepEqual(blockAction.label, {value: "Secondary Button 2", attributes: {accesskey: "a"}});
- sandbox.spy(pageAction, "hideAddressBarNotifier");
- sandbox.spy(pageAction, "_blockMessage");
- CFRPageActions.RecommendationMap.set(fakeBrowser, {});
- blockAction.callback();
- assert.calledOnce(pageAction.hideAddressBarNotifier);
- assert.calledOnce(pageAction._blockMessage);
- // Should send telemetry
- assert.calledWith(dispatchStub, {
- type: "DOORHANGER_TELEMETRY",
- data: {
- action: "cfr_user_event",
- source: "CFR",
- message_id: fakeRecommendation.id,
- bucket_id: fakeRecommendation.content.bucket_id,
- event: "BLOCK",
- },
- });
- // Should remove the recommendation
- assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
- });
- it("should send right telemetry for MANAGE secondary action", async () => {
- await pageAction._showPopupOnClick();
- const manageAction = global.PopupNotifications.show.firstCall.args[5][2]; // eslint-disable-line prefer-destructuring
- assert.deepEqual(manageAction.label, {value: "Secondary Button 3", attributes: {accesskey: "g"}});
- sandbox.spy(pageAction, "hideAddressBarNotifier");
- CFRPageActions.RecommendationMap.set(fakeBrowser, {});
- manageAction.callback();
- // Should send telemetry
- assert.calledWith(dispatchStub, {
- type: "DOORHANGER_TELEMETRY",
- data: {
- action: "cfr_user_event",
- source: "CFR",
- message_id: fakeRecommendation.id,
- bucket_id: fakeRecommendation.content.bucket_id,
- event: "MANAGE",
- },
- });
- // Don't remove the recommendation on `MANAGE` action
- assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
- assert.notCalled(pageAction.hideAddressBarNotifier);
- });
- it("should call PopupNotifications.show with the right arguments", async () => {
- await pageAction._showPopupOnClick();
- assert.calledWith(
- global.PopupNotifications.show,
- fakeBrowser,
- "contextual-feature-recommendation",
- fakeRecommendation.content.addon.title,
- "cfr",
- sinon.match.any, // Corresponds to the main action, tested above
- sinon.match.any, // Corresponds to the secondary action, tested above
- {
- popupIconURL: fakeRecommendation.content.addon.icon,
- hideClose: true,
- eventCallback: pageAction._popupStateChange,
- }
- );
- });
- it("should show the bullet list details", async () => {
- delete fakeRecommendation.content.addon;
- await pageAction._showPopupOnClick();
- assert.calledOnce(translateElementsStub);
- });
- it("should set the data-l10n-id on the list element", async () => {
- delete fakeRecommendation.content.addon;
- await pageAction._showPopupOnClick();
- assert.calledOnce(setAttributesStub);
- assert.calledWith(setAttributesStub, sinon.match.any, fakeRecommendation.content.descriptionDetails.steps[0].string_id);
- });
- it("should set the correct data-notification-category", async () => {
- delete fakeRecommendation.content.addon;
- await pageAction._showPopupOnClick();
- assert.equal(elements["contextual-feature-recommendation-notification"].dataset.notificationCategory, fakeRecommendation.content.category);
- });
- it("should send PIN event on primary action click", async () => {
- sandbox.stub(pageAction, "_sendTelemetry");
- delete fakeRecommendation.content.addon;
- await pageAction._showPopupOnClick();
- const [, , , , {callback}] = global.PopupNotifications.show.firstCall.args;
- callback();
- // First call is triggered by `_showPopupOnClick`
- assert.propertyVal(pageAction._sendTelemetry.secondCall.args[0], "event", "PIN");
- });
- });
- });
- describe("CFRPageActions", () => {
- beforeEach(() => {
- // Spy on the prototype methods to inspect calls for any PageAction instance
- sandbox.spy(PageAction.prototype, "showAddressBarNotifier");
- sandbox.spy(PageAction.prototype, "hideAddressBarNotifier");
- });
- describe("updatePageActions", () => {
- let savedRec;
- beforeEach(() => {
- const win = fakeBrowser.ownerGlobal;
- CFRPageActions.PageActionMap.set(win, new PageAction(win, dispatchStub));
- const {id, content} = fakeRecommendation;
- savedRec = {
- id,
- host: fakeHost,
- content,
- };
- CFRPageActions.RecommendationMap.set(fakeBrowser, savedRec);
- });
- it("should do nothing if a pageAction doesn't exist for the window", () => {
- const win = fakeBrowser.ownerGlobal;
- CFRPageActions.PageActionMap.delete(win);
- CFRPageActions.updatePageActions(fakeBrowser);
- assert.notCalled(PageAction.prototype.showAddressBarNotifier);
- assert.notCalled(PageAction.prototype.hideAddressBarNotifier);
- });
- it("should do nothing if the browser is not the `selectedBrowser`", () => {
- const someOtherFakeBrowser = {};
- CFRPageActions.updatePageActions(someOtherFakeBrowser);
- assert.notCalled(PageAction.prototype.showAddressBarNotifier);
- assert.notCalled(PageAction.prototype.hideAddressBarNotifier);
- });
- it("should hideAddressBarNotifier the pageAction if a recommendation doesn't exist for the given browser", () => {
- CFRPageActions.RecommendationMap.delete(fakeBrowser);
- CFRPageActions.updatePageActions(fakeBrowser);
- assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
- });
- it("should show the pageAction if a recommendation exists and the host matches", () => {
- CFRPageActions.updatePageActions(fakeBrowser);
- assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
- assert.calledWith(PageAction.prototype.showAddressBarNotifier, savedRec);
- });
- it("should hideAddressBarNotifier the pageAction and delete the recommendation if the recommendation exists but the host doesn't match", () => {
- const someOtherFakeHost = "subdomain.mozilla.com";
- fakeBrowser.documentURI.host = someOtherFakeHost;
- assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
- CFRPageActions.updatePageActions(fakeBrowser);
- assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
- assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
- });
- it("should not call `delete` if retain is true", () => {
- savedRec.retain = true;
- fakeBrowser.documentURI.host = "subdomain.mozilla.com";
- assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
- CFRPageActions.updatePageActions(fakeBrowser);
- assert.propertyVal(savedRec, "retain", false);
- assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
- assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
- });
- it("should call `delete` if retain is false", () => {
- savedRec.retain = false;
- fakeBrowser.documentURI.host = "subdomain.mozilla.com";
- assert.isTrue(CFRPageActions.RecommendationMap.has(fakeBrowser));
- CFRPageActions.updatePageActions(fakeBrowser);
- assert.propertyVal(savedRec, "retain", false);
- assert.calledOnce(PageAction.prototype.hideAddressBarNotifier);
- assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
- });
- });
- describe("forceRecommendation", () => {
- it("should succeed and add an element to the RecommendationMap", async () => {
- assert.isTrue(await CFRPageActions.forceRecommendation({browser: fakeBrowser}, fakeRecommendation, dispatchStub));
- assert.deepInclude(CFRPageActions.RecommendationMap.get(fakeBrowser), {
- id: fakeRecommendation.id,
- content: fakeRecommendation.content,
- });
- });
- it("should create a PageAction if one doesn't exist for the window, save it in the PageActionMap, and call `show`", async () => {
- const win = fakeBrowser.ownerGlobal;
- assert.isFalse(CFRPageActions.PageActionMap.has(win));
- await CFRPageActions.forceRecommendation({browser: fakeBrowser}, fakeRecommendation, dispatchStub);
- const pageAction = CFRPageActions.PageActionMap.get(win);
- assert.equal(win, pageAction.window);
- assert.equal(dispatchStub, pageAction._dispatchToASRouter);
- assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
- });
- });
- describe("addRecommendation", () => {
- it("should fail and not add a recommendation if the browser is part of a private window", async () => {
- global.PrivateBrowsingUtils.isWindowPrivate.returns(true);
- assert.isFalse(await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub));
- assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
- });
- it("should fail and not add a recommendation if the browser is not the selected browser", async () => {
- global.gBrowser.selectedBrowser = {}; // Some other browser
- assert.isFalse(await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub));
- });
- it("should fail and not add a recommendation if the host doesn't match", async () => {
- const someOtherFakeHost = "subdomain.mozilla.com";
- assert.isFalse(await CFRPageActions.addRecommendation(fakeBrowser, someOtherFakeHost, fakeRecommendation, dispatchStub));
- });
- it("should otherwise succeed and add an element to the RecommendationMap", async () => {
- assert.isTrue(await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub));
- assert.deepInclude(CFRPageActions.RecommendationMap.get(fakeBrowser), {
- id: fakeRecommendation.id,
- host: fakeHost,
- content: fakeRecommendation.content,
- });
- });
- it("should create a PageAction if one doesn't exist for the window, save it in the PageActionMap, and call `show`", async () => {
- const win = fakeBrowser.ownerGlobal;
- assert.isFalse(CFRPageActions.PageActionMap.has(win));
- await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
- const pageAction = CFRPageActions.PageActionMap.get(win);
- assert.equal(win, pageAction.window);
- assert.equal(dispatchStub, pageAction._dispatchToASRouter);
- assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
- });
- it("should add the right url if we fetched and addon install URL", async () => {
- fakeRecommendation.template = "cfr_doorhanger";
- await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
- const recommendation = CFRPageActions.RecommendationMap.get(fakeBrowser);
- // sanity check - just go through some of the rest of the attributes to make sure they were untouched
- assert.equal(recommendation.id, fakeRecommendation.id);
- assert.equal(recommendation.content.heading_text, fakeRecommendation.content.heading_text);
- assert.equal(recommendation.content.addon, fakeRecommendation.content.addon);
- assert.equal(recommendation.content.text, fakeRecommendation.content.text);
- assert.equal(recommendation.content.buttons.secondary, fakeRecommendation.content.buttons.secondary);
- assert.equal(recommendation.content.buttons.primary.action.id, fakeRecommendation.content.buttons.primary.action.id);
- delete fakeRecommendation.template;
- });
- it("should prevent a second message if one is currently displayed", async () => {
- const secondMessage = {...fakeRecommendation, id: "second_message"};
- let messageAdded = await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
- assert.isTrue(messageAdded);
- assert.deepInclude(CFRPageActions.RecommendationMap.get(fakeBrowser), {
- id: fakeRecommendation.id,
- host: fakeHost,
- content: fakeRecommendation.content,
- });
- messageAdded = await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, secondMessage, dispatchStub);
- // Adding failed
- assert.isFalse(messageAdded);
- // First message is still there
- assert.deepInclude(CFRPageActions.RecommendationMap.get(fakeBrowser), {
- id: fakeRecommendation.id,
- host: fakeHost,
- content: fakeRecommendation.content,
- });
- });
- it("should send impressions just for the first message", async () => {
- const secondMessage = {...fakeRecommendation, id: "second_message"};
- await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
- await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, secondMessage, dispatchStub);
- // Doorhanger telemetry + Impression for just 1 message
- assert.calledTwice(dispatchStub);
- const [firstArgs] = dispatchStub.firstCall.args;
- const [secondArgs] = dispatchStub.secondCall.args;
- assert.equal(firstArgs.data.id, secondArgs.data.message_id);
- });
- });
- describe("clearRecommendations", () => {
- const createFakePageAction = () => ({hideAddressBarNotifier: sandbox.stub()});
- const windows = [{}, {}, {closed: true}];
- const browsers = [{}, {}, {}, {}];
- beforeEach(() => {
- CFRPageActions.PageActionMap.set(windows[0], createFakePageAction());
- CFRPageActions.PageActionMap.set(windows[2], createFakePageAction());
- for (const browser of browsers) {
- CFRPageActions.RecommendationMap.set(browser, {});
- }
- globals.set({Services: {wm: {getEnumerator: () => windows}}});
- });
- it("should hideAddressBarNotifier the PageActions of any existing, non-closed windows", () => {
- const pageActions = windows.map(win => CFRPageActions.PageActionMap.get(win));
- CFRPageActions.clearRecommendations();
- // Only the first window had a PageAction and wasn't closed
- assert.calledOnce(pageActions[0].hideAddressBarNotifier);
- assert.isUndefined(pageActions[1]);
- assert.notCalled(pageActions[2].hideAddressBarNotifier);
- });
- it("should clear the PageActionMap and the RecommendationMap", () => {
- CFRPageActions.clearRecommendations();
- // Both are WeakMaps and so are not iterable, cannot be cleared, and
- // cannot have their length queried directly, so we have to check
- // whether previous elements still exist
- assert.lengthOf(windows, 3);
- for (const win of windows) {
- assert.isFalse(CFRPageActions.PageActionMap.has(win));
- }
- assert.lengthOf(browsers, 4);
- for (const browser of browsers) {
- assert.isFalse(CFRPageActions.RecommendationMap.has(browser));
- }
- });
- });
- });
- });
|