gecko-dev/browser/components/newtab/test/unit/asrouter/CFRPageActions.test.js
k88hudson 109f2177c9 Bug 1494275 - Support dynamic install urls for CFR r=ursula
Differential Revision: https://phabricator.services.mozilla.com/D6974

--HG--
extra : moz-landing-system : lando
2018-09-26 19:19:24 +00:00

702 lines
30 KiB
JavaScript

import {CFRPageActions, PageAction} from "lib/CFRPageActions.jsm";
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-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",
];
beforeEach(() => {
sandbox = sinon.sandbox.create();
clock = sandbox.useFakeTimers();
fakeRecommendation = {
id: "fake_id",
content: {
bucket_id: "fake_bucket_id",
notification_text: "Fake Notification Text",
info_icon: {
label: "Fake Info Icon Label",
sumo_path: "a_help_path_fragment",
},
heading_text: "Fake Heading Text",
addon: {
title: "Fake Addon Title",
author: "Fake Addon Author",
icon: "a_path_to_some_icon",
rating: 4.2,
users: 1234,
amo_url: "a_path_to_amo",
},
text: "Here is the recommendation text body",
buttons: {
primary: {
label: {string_id: "primary_button_id"},
action: {id: "primary_action"},
},
secondary: {
label: {string_id: "secondary_button_id"},
action: {id: "secondary_action"},
},
},
},
};
fakeHost = "mozilla.org";
fakeBrowser = {
documentURI: {
scheme: "https",
host: fakeHost,
},
ownerGlobal: window,
};
dispatchStub = sandbox.stub();
globals = new GlobalOverrider();
globals.set({
Localization: class {},
promiseDocumentFlushed: Promise.resolve(),
PopupNotifications: {
show: sandbox.stub(),
remove: sandbox.stub(),
},
PrivateBrowsingUtils: {isWindowPrivate: sandbox.stub().returns(false)},
gBrowser: {selectedBrowser: fakeBrowser},
});
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;
}
});
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("#show", () => {
it("should un-hide 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.show(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");
const promiseGetterStub = sandbox.stub().resolves();
sandbox.stub(global, "promiseDocumentFlushed").get(promiseGetterStub);
await pageAction.show(fakeRecommendation);
assert.calledOnce(promiseGetterStub);
assert.callOrder(promiseGetterStub, pageAction.label.getClientRects);
});
it("should set the CSS variable --cfr-label-width correctly", async () => {
await pageAction.show(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.show(fakeRecommendation);
assert.notCalled(pageAction._dispatchImpression);
clock.tick(1001);
assert.notEqual(pageAction.urlbar.getAttribute("cfr-recommendation-state"), "expanded");
await pageAction.show(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.show(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("#hide", () => {
it("should hide the container, cancel any state changes, and remove the state attribute", () => {
sandbox.spy(pageAction, "_clearScheduledStateChanges");
pageAction.hide();
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.hide();
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.Localization.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("#_handleClick", () => {
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(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
});
it("should call `.hide` and do nothing if there is no recommendation for the selected browser", async () => {
sandbox.spy(pageAction, "hide");
CFRPageActions.RecommendationMap.delete(fakeBrowser);
await pageAction._handleClick({});
assert.calledOnce(pageAction.hide);
assert.notCalled(global.PopupNotifications.show);
});
it("should cancel any planned state changes", async () => {
sandbox.spy(pageAction, "_clearScheduledStateChanges");
assert.notCalled(pageAction._clearScheduledStateChanges);
await pageAction._handleClick({});
assert.calledOnce(pageAction._clearScheduledStateChanges);
});
it("should set the right text values", async () => {
await pageAction._handleClick({});
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._handleClick();
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._handleClick();
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._handleClick();
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 () => {
await pageAction._handleClick();
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, "hide");
mainAction.callback();
assert.calledOnce(pageAction.hide);
// 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"}},
{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._handleClick();
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, "hide");
CFRPageActions.RecommendationMap.set(fakeBrowser, {});
secondaryAction.callback();
assert.calledOnce(pageAction.hide);
// 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",
},
});
// Should remove the recommendation
assert.isFalse(CFRPageActions.RecommendationMap.has(fakeBrowser));
});
it("should call PopupNotifications.show with the right arguments", async () => {
await pageAction._handleClick();
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,
}
);
});
});
});
describe("CFRPageActions", () => {
beforeEach(() => {
// Spy on the prototype methods to inspect calls for any PageAction instance
sandbox.spy(PageAction.prototype, "show");
sandbox.spy(PageAction.prototype, "hide");
});
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.show);
assert.notCalled(PageAction.prototype.hide);
});
it("should do nothing if the browser is not the `selectedBrowser`", () => {
const someOtherFakeBrowser = {};
CFRPageActions.updatePageActions(someOtherFakeBrowser);
assert.notCalled(PageAction.prototype.show);
assert.notCalled(PageAction.prototype.hide);
});
it("should hide the pageAction if a recommendation doesn't exist for the given browser", () => {
CFRPageActions.RecommendationMap.delete(fakeBrowser);
CFRPageActions.updatePageActions(fakeBrowser);
assert.calledOnce(PageAction.prototype.hide);
});
it("should show the pageAction if a recommendation exists and the host matches", () => {
CFRPageActions.updatePageActions(fakeBrowser);
assert.calledOnce(PageAction.prototype.show);
assert.calledWith(PageAction.prototype.show, savedRec);
});
it("should hide 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.hide);
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.show);
});
});
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.show);
});
it("should add the right url if we fetched and addon install URL", async () => {
sinon.stub(CFRPageActions, "_fetchLatestAddonVersion").returns(Promise.resolve("latest-addon.xpi"));
fakeRecommendation.template = "cfr_doorhanger";
await CFRPageActions.addRecommendation(fakeBrowser, fakeHost, fakeRecommendation, dispatchStub);
const recommendation = CFRPageActions.RecommendationMap.get(fakeBrowser);
assert.equal(recommendation.content.buttons.primary.action.data.url, "latest-addon.xpi");
// 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;
});
});
describe("clearRecommendations", () => {
const createFakePageAction = () => ({hide: 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 hide 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].hide);
assert.isUndefined(pageActions[1]);
assert.notCalled(pageActions[2].hide);
});
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));
}
});
});
});
});