Bug 1561536 - Add new message schema and template type for feature callouts (#5133)
This commit is contained in:
Родитель
39b6db8257
Коммит
27b795648d
|
@ -34,6 +34,7 @@ Please note that some targeting attributes require stricter controls on the tele
|
|||
* [isFxAEnabled](#isFxAEnabled)
|
||||
* [xpinstallEnabled](#xpinstallEnabled)
|
||||
* [hasPinnedTabs](#haspinnedtabs)
|
||||
* [hasAccessedFxAPanel](#hasaccessedfxapanel)
|
||||
|
||||
## Detailed usage
|
||||
|
||||
|
@ -474,3 +475,13 @@ Does the user have any pinned tabs in any windows.
|
|||
```ts
|
||||
declare const hasPinnedTabs: boolean;
|
||||
```
|
||||
|
||||
### `hasAccessedFxAPanel`
|
||||
|
||||
Boolean pref that gets set the first time the user opens the FxA toolbar panel
|
||||
|
||||
#### Definition
|
||||
|
||||
```ts
|
||||
declare const hasAccessedFxAPanel: boolean;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"title": "ToolbarBadgeMessage",
|
||||
"description": "A template that specifies to which element in the browser toolbar to add a notification.",
|
||||
"version": "1.0.0",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["target"]
|
||||
}
|
|
@ -18,6 +18,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
SnippetsTestMessageProvider:
|
||||
"resource://activity-stream/lib/SnippetsTestMessageProvider.jsm",
|
||||
PanelTestProvider: "resource://activity-stream/lib/PanelTestProvider.jsm",
|
||||
ToolbarBadgeHub: "resource://activity-stream/lib/ToolbarBadgeHub.jsm",
|
||||
});
|
||||
const {
|
||||
ASRouterActions: ra,
|
||||
|
@ -491,11 +492,13 @@ class _ASRouter {
|
|||
};
|
||||
this._triggerHandler = this._triggerHandler.bind(this);
|
||||
this._localProviders = localProviders;
|
||||
this.blockMessageById = this.blockMessageById.bind(this);
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
this.handleMessageRequest = this.handleMessageRequest.bind(this);
|
||||
this.addImpression = this.addImpression.bind(this);
|
||||
this._handleTargetingError = this._handleTargetingError.bind(this);
|
||||
this.onPrefChange = this.onPrefChange.bind(this);
|
||||
this.dispatch = this.dispatch.bind(this);
|
||||
}
|
||||
|
||||
async onPrefChange(prefName) {
|
||||
|
@ -712,7 +715,6 @@ class _ASRouter {
|
|||
this._storage = storage;
|
||||
this.WHITELIST_HOSTS = this._loadSnippetsWhitelistHosts();
|
||||
this.dispatchToAS = dispatchToAS;
|
||||
this.dispatch = this.dispatch.bind(this);
|
||||
|
||||
ASRouterPreferences.init();
|
||||
ASRouterPreferences.addListener(this.onPrefChange);
|
||||
|
@ -721,6 +723,11 @@ class _ASRouter {
|
|||
this.addImpression,
|
||||
this.dispatch
|
||||
);
|
||||
ToolbarBadgeHub.init(this.waitForInitialized, {
|
||||
handleMessageRequest: this.handleMessageRequest,
|
||||
addImpression: this.addImpression,
|
||||
blockMessageById: this.blockMessageById,
|
||||
});
|
||||
|
||||
this._loadLocalProviders();
|
||||
|
||||
|
@ -1253,6 +1260,9 @@ class _ASRouter {
|
|||
BookmarkPanelHub._forceShowMessage(target, message);
|
||||
}
|
||||
break;
|
||||
case "toolbar_badge":
|
||||
ToolbarBadgeHub.registerBadgeNotificationListener(message);
|
||||
break;
|
||||
default:
|
||||
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
|
||||
type: "SET_MESSAGE",
|
||||
|
@ -1446,12 +1456,18 @@ class _ASRouter {
|
|||
await this._sendMessageToTarget(message, target, trigger);
|
||||
}
|
||||
|
||||
handleMessageRequest(trigger) {
|
||||
const msgs = this._getUnblockedMessages();
|
||||
return this._findMessage(
|
||||
msgs.filter(m => m.trigger && m.trigger.id === trigger.id),
|
||||
trigger
|
||||
);
|
||||
handleMessageRequest({ triggerId, template }) {
|
||||
const msgs = this._getUnblockedMessages().filter(m => {
|
||||
if (template && m.template !== template) {
|
||||
return false;
|
||||
}
|
||||
if (m.trigger && m.trigger.id !== triggerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return this._findMessage(msgs, { id: triggerId });
|
||||
}
|
||||
|
||||
async setMessageById(id, target, force = true, action = {}) {
|
||||
|
|
|
@ -359,6 +359,12 @@ const TargetingGetters = {
|
|||
|
||||
return false;
|
||||
},
|
||||
get hasAccessedFxAPanel() {
|
||||
return Services.prefs.getBoolPref(
|
||||
"identity.fxaccounts.toolbar.accessed",
|
||||
true
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
this.ASRouterTargeting = {
|
||||
|
|
|
@ -85,7 +85,9 @@ class _BookmarkPanelHub {
|
|||
// If we didn't match on a previously cached request then make sure
|
||||
// the container is empty
|
||||
this._removeContainer(target);
|
||||
const response = await this._handleMessageRequest(this._trigger);
|
||||
const response = await this._handleMessageRequest({
|
||||
triggerId: this._trigger.id,
|
||||
});
|
||||
|
||||
return this.onResponse(response, target, win);
|
||||
}
|
||||
|
|
|
@ -465,6 +465,16 @@ const ONBOARDING_MESSAGES = async () => [
|
|||
"attributionData.campaign == 'non-fx-button' && attributionData.source == 'addons.mozilla.org'",
|
||||
trigger: { id: "firstRun" },
|
||||
},
|
||||
{
|
||||
id: "FXA_ACCOUNTS_BADGE",
|
||||
template: "toolbar_badge",
|
||||
content: {
|
||||
target: "fxa-toolbar-menu-button",
|
||||
},
|
||||
// Never accessed the FxA panel && doesn't use Firefox sync & has FxA enabled
|
||||
targeting: `!hasAccessedFxAPanel && !usesFirefoxSync && isFxAEnabled == true`,
|
||||
trigger: { id: "toolbarBadgeUpdate" },
|
||||
},
|
||||
];
|
||||
|
||||
const OnboardingMessageProvider = {
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"EveryWindow",
|
||||
"resource:///modules/EveryWindow.jsm"
|
||||
);
|
||||
|
||||
const notificationsByWindow = new WeakMap();
|
||||
|
||||
class _ToolbarBadgeHub {
|
||||
constructor() {
|
||||
this.id = "toolbar-badge-hub";
|
||||
this.template = "toolbar_badge";
|
||||
this.state = null;
|
||||
this.removeAllNotifications = this.removeAllNotifications.bind(this);
|
||||
this.removeToolbarNotification = this.removeToolbarNotification.bind(this);
|
||||
this.addToolbarNotification = this.addToolbarNotification.bind(this);
|
||||
|
||||
this._handleMessageRequest = null;
|
||||
this._addImpression = null;
|
||||
this._blockMessageById = null;
|
||||
}
|
||||
|
||||
async init(
|
||||
waitForInitialized,
|
||||
{ handleMessageRequest, addImpression, blockMessageById }
|
||||
) {
|
||||
this._handleMessageRequest = handleMessageRequest;
|
||||
this._blockMessageById = blockMessageById;
|
||||
this._addImpression = addImpression;
|
||||
// Need to wait for ASRouter to initialize before trying to fetch messages
|
||||
await waitForInitialized;
|
||||
this.messageRequest("toolbarBadgeUpdate");
|
||||
}
|
||||
|
||||
removeAllNotifications() {
|
||||
// Will call uninit on every window
|
||||
EveryWindow.unregisterCallback(this.id);
|
||||
this._blockMessageById(this.state.notification.id);
|
||||
this.state = null;
|
||||
}
|
||||
|
||||
removeToolbarNotification(toolbarButton) {
|
||||
toolbarButton
|
||||
.querySelector(".toolbarbutton-badge")
|
||||
.removeAttribute("value");
|
||||
toolbarButton.removeAttribute("badged");
|
||||
}
|
||||
|
||||
addToolbarNotification(win, message) {
|
||||
const document = win.browser.ownerDocument;
|
||||
let toolbarbutton = document.getElementById(message.content.target);
|
||||
if (toolbarbutton) {
|
||||
toolbarbutton.setAttribute("badged", true);
|
||||
toolbarbutton
|
||||
.querySelector(".toolbarbutton-badge")
|
||||
.setAttribute("value", "x");
|
||||
|
||||
toolbarbutton.addEventListener("click", this.removeAllNotifications, {
|
||||
once: true,
|
||||
});
|
||||
this.state = { notification: { id: message.id } };
|
||||
|
||||
return toolbarbutton;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
registerBadgeNotificationListener(message) {
|
||||
this._addImpression(message);
|
||||
|
||||
EveryWindow.registerCallback(
|
||||
this.id,
|
||||
win => {
|
||||
if (notificationsByWindow.has(win)) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
const el = this.addToolbarNotification(win, message);
|
||||
notificationsByWindow.set(win, el);
|
||||
},
|
||||
win => {
|
||||
const el = notificationsByWindow.get(win);
|
||||
this.removeToolbarNotification(el);
|
||||
notificationsByWindow.delete(win);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async messageRequest(triggerId) {
|
||||
const message = await this._handleMessageRequest({
|
||||
triggerId,
|
||||
template: this.template,
|
||||
});
|
||||
if (message) {
|
||||
this.registerBadgeNotificationListener(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._ToolbarBadgeHub = _ToolbarBadgeHub;
|
||||
|
||||
/**
|
||||
* ToolbarBadgeHub - singleton instance of _ToolbarBadgeHub that can initiate
|
||||
* message requests and render messages.
|
||||
*/
|
||||
this.ToolbarBadgeHub = new _ToolbarBadgeHub();
|
||||
|
||||
const EXPORTED_SYMBOLS = ["ToolbarBadgeHub", "_ToolbarBadgeHub"];
|
|
@ -807,6 +807,22 @@ add_task(async function check_pinned_tabs() {
|
|||
);
|
||||
});
|
||||
|
||||
add_task(async function check_hasAccessedFxAPanel() {
|
||||
is(
|
||||
await ASRouterTargeting.Environment.hasAccessedFxAPanel,
|
||||
false,
|
||||
"Not accessed yet"
|
||||
);
|
||||
|
||||
await pushPrefs(["identity.fxaccounts.toolbar.accessed", true]);
|
||||
|
||||
is(
|
||||
await ASRouterTargeting.Environment.hasAccessedFxAPanel,
|
||||
true,
|
||||
"Should detect panel access"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function checkCFRPinnedTabsTargetting() {
|
||||
const now = Date.now();
|
||||
const timeMinutesAgo = numMinutes => now - numMinutes * 60 * 1000;
|
||||
|
|
|
@ -66,6 +66,7 @@ describe("ASRouter", () => {
|
|||
let dispatchStub;
|
||||
let fakeAttributionCode;
|
||||
let FakeBookmarkPanelHub;
|
||||
let FakeToolbarBadgeHub;
|
||||
|
||||
function createFakeStorage() {
|
||||
const getStub = sandbox.stub();
|
||||
|
@ -139,6 +140,10 @@ describe("ASRouter", () => {
|
|||
uninit: sandbox.stub(),
|
||||
_forceShowMessage: sandbox.stub(),
|
||||
};
|
||||
FakeToolbarBadgeHub = {
|
||||
init: sandbox.stub(),
|
||||
registerBadgeNotificationListener: sandbox.stub(),
|
||||
};
|
||||
globals.set({
|
||||
AttributionCode: fakeAttributionCode,
|
||||
// Testing framework doesn't know how to `defineLazyModuleGetter` so we're
|
||||
|
@ -146,6 +151,7 @@ describe("ASRouter", () => {
|
|||
SnippetsTestMessageProvider,
|
||||
PanelTestProvider,
|
||||
BookmarkPanelHub: FakeBookmarkPanelHub,
|
||||
ToolbarBadgeHub: FakeToolbarBadgeHub,
|
||||
});
|
||||
await createRouterAndInit();
|
||||
});
|
||||
|
@ -412,6 +418,75 @@ describe("ASRouter", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#routeMessageToTarget", () => {
|
||||
let target;
|
||||
beforeEach(() => {
|
||||
sandbox.stub(CFRPageActions, "forceRecommendation");
|
||||
sandbox.stub(CFRPageActions, "addRecommendation");
|
||||
target = { sendAsyncMessage: sandbox.stub() };
|
||||
});
|
||||
it("should route toolbar_badge message to the right hub", () => {
|
||||
Router.routeMessageToTarget({ template: "toolbar_badge" }, target);
|
||||
|
||||
assert.calledOnce(FakeToolbarBadgeHub.registerBadgeNotificationListener);
|
||||
assert.notCalled(FakeBookmarkPanelHub._forceShowMessage);
|
||||
assert.notCalled(CFRPageActions.addRecommendation);
|
||||
assert.notCalled(CFRPageActions.forceRecommendation);
|
||||
assert.notCalled(target.sendAsyncMessage);
|
||||
});
|
||||
it("should route fxa_bookmark_panel message to the right hub force = true", () => {
|
||||
Router.routeMessageToTarget(
|
||||
{ template: "fxa_bookmark_panel" },
|
||||
target,
|
||||
{},
|
||||
true
|
||||
);
|
||||
|
||||
assert.calledOnce(FakeBookmarkPanelHub._forceShowMessage);
|
||||
assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
|
||||
assert.notCalled(CFRPageActions.addRecommendation);
|
||||
assert.notCalled(CFRPageActions.forceRecommendation);
|
||||
assert.notCalled(target.sendAsyncMessage);
|
||||
});
|
||||
it("should route cfr_doorhanger message to the right hub force = false", () => {
|
||||
Router.routeMessageToTarget(
|
||||
{ template: "cfr_doorhanger" },
|
||||
target,
|
||||
{ param: {} },
|
||||
false
|
||||
);
|
||||
|
||||
assert.calledOnce(CFRPageActions.addRecommendation);
|
||||
assert.notCalled(FakeBookmarkPanelHub._forceShowMessage);
|
||||
assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
|
||||
assert.notCalled(CFRPageActions.forceRecommendation);
|
||||
assert.notCalled(target.sendAsyncMessage);
|
||||
});
|
||||
it("should route cfr_doorhanger message to the right hub force = true", () => {
|
||||
Router.routeMessageToTarget(
|
||||
{ template: "cfr_doorhanger" },
|
||||
target,
|
||||
{},
|
||||
true
|
||||
);
|
||||
|
||||
assert.calledOnce(CFRPageActions.forceRecommendation);
|
||||
assert.notCalled(CFRPageActions.addRecommendation);
|
||||
assert.notCalled(FakeBookmarkPanelHub._forceShowMessage);
|
||||
assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
|
||||
assert.notCalled(target.sendAsyncMessage);
|
||||
});
|
||||
it("should route default to sending to content", () => {
|
||||
Router.routeMessageToTarget({ template: "snippets" }, target, {}, true);
|
||||
|
||||
assert.calledOnce(target.sendAsyncMessage);
|
||||
assert.notCalled(CFRPageActions.forceRecommendation);
|
||||
assert.notCalled(CFRPageActions.addRecommendation);
|
||||
assert.notCalled(FakeBookmarkPanelHub._forceShowMessage);
|
||||
assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#loadMessagesFromAllProviders", () => {
|
||||
function assertRouterContainsMessages(messages) {
|
||||
const messageIdsInRouter = Router.state.messages.map(m => m.id);
|
||||
|
@ -691,18 +766,47 @@ describe("ASRouter", () => {
|
|||
|
||||
describe("#handleMessageRequest", () => {
|
||||
it("should get unblocked messages that match the trigger", async () => {
|
||||
const message = {
|
||||
const message1 = {
|
||||
id: "1",
|
||||
campaign: "foocampaign",
|
||||
trigger: { id: "foo" },
|
||||
};
|
||||
await Router.setState({ messages: [message] });
|
||||
const message2 = {
|
||||
id: "2",
|
||||
campaign: "foocampaign",
|
||||
trigger: { id: "bar" },
|
||||
};
|
||||
await Router.setState({ messages: [message2, message1] });
|
||||
// Just return the first message provided as arg
|
||||
sandbox.stub(Router, "_findMessage").callsFake(messages => messages[0]);
|
||||
|
||||
const result = Router.handleMessageRequest({ id: "foo" });
|
||||
const result = Router.handleMessageRequest({ triggerId: "foo" });
|
||||
|
||||
assert.deepEqual(result, message);
|
||||
assert.deepEqual(result, message1);
|
||||
});
|
||||
it("should get unblocked messages that match trigger and template", async () => {
|
||||
const message1 = {
|
||||
id: "1",
|
||||
campaign: "foocampaign",
|
||||
template: "badge",
|
||||
trigger: { id: "foo" },
|
||||
};
|
||||
const message2 = {
|
||||
id: "2",
|
||||
campaign: "foocampaign",
|
||||
template: "snippet",
|
||||
trigger: { id: "foo" },
|
||||
};
|
||||
await Router.setState({ messages: [message2, message1] });
|
||||
// Just return the first message provided as arg
|
||||
sandbox.stub(Router, "_findMessage").callsFake(messages => messages[0]);
|
||||
|
||||
const result = Router.handleMessageRequest({
|
||||
triggerId: "foo",
|
||||
template: "badge",
|
||||
});
|
||||
|
||||
assert.deepEqual(result, message1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ describe("ASRouterFeed", () => {
|
|||
let storage;
|
||||
let globals;
|
||||
let FakeBookmarkPanelHub;
|
||||
let FakeToolbarBadgeHub;
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
globals = new GlobalOverrider();
|
||||
|
@ -19,7 +20,11 @@ describe("ASRouterFeed", () => {
|
|||
init: sandbox.stub(),
|
||||
uninit: sandbox.stub(),
|
||||
};
|
||||
FakeToolbarBadgeHub = {
|
||||
init: sandbox.stub(),
|
||||
};
|
||||
globals.set("BookmarkPanelHub", FakeBookmarkPanelHub);
|
||||
globals.set("ToolbarBadgeHub", FakeToolbarBadgeHub);
|
||||
|
||||
Router = new _ASRouter({ providers: [FAKE_LOCAL_PROVIDER] });
|
||||
storage = {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { GlobalOverrider } from "test/unit/utils";
|
||||
import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
|
||||
import schema from "content-src/asrouter/templates/OnboardingMessage/OnboardingMessage.schema.json";
|
||||
import badgeSchema from "content-src/asrouter/templates/OnboardingMessage/ToolbarBadgeMessage.schema.json";
|
||||
|
||||
const DEFAULT_CONTENT = {
|
||||
title: "A title",
|
||||
|
@ -61,6 +62,13 @@ describe("OnboardingMessage", () => {
|
|||
.filter(msg => msg.template in ["onboarding", "return_to_amo_overlay"])
|
||||
.forEach(msg => assert.jsonSchema(msg.content, schema));
|
||||
});
|
||||
it("should validate all badge template messages", async () => {
|
||||
const messages = await OnboardingMessageProvider.getUntranslatedMessages();
|
||||
|
||||
messages
|
||||
.filter(msg => msg.template === "toolbar_badge")
|
||||
.forEach(msg => assert.jsonSchema(msg.content, badgeSchema));
|
||||
});
|
||||
it("should decode the content field (double decoding)", async () => {
|
||||
const fakeContent = "foo%2540bar.org";
|
||||
globals.set("AttributionCode", {
|
||||
|
|
|
@ -131,7 +131,9 @@ describe("BookmarkPanelHub", () => {
|
|||
await instance.messageRequest(fakeTarget, {});
|
||||
|
||||
assert.calledOnce(fakeHandleMessageRequest);
|
||||
assert.calledWithExactly(fakeHandleMessageRequest, instance._trigger);
|
||||
assert.calledWithExactly(fakeHandleMessageRequest, {
|
||||
triggerId: instance._trigger.id,
|
||||
});
|
||||
});
|
||||
it("should call onResponse", async () => {
|
||||
fakeHandleMessageRequest.resolves(fakeMessage);
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
import { _ToolbarBadgeHub } from "lib/ToolbarBadgeHub.jsm";
|
||||
import { GlobalOverrider } from "test/unit/utils";
|
||||
import { OnboardingMessageProvider } from "lib/OnboardingMessageProvider.jsm";
|
||||
|
||||
describe("BookmarkPanelHub", () => {
|
||||
let sandbox;
|
||||
let instance;
|
||||
let fakeAddImpression;
|
||||
let fxaMessage;
|
||||
let fakeElement;
|
||||
let globals;
|
||||
let everyWindowStub;
|
||||
beforeEach(async () => {
|
||||
globals = new GlobalOverrider();
|
||||
sandbox = sinon.createSandbox();
|
||||
instance = new _ToolbarBadgeHub();
|
||||
fakeAddImpression = sandbox.stub();
|
||||
[
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
,
|
||||
fxaMessage,
|
||||
] = await OnboardingMessageProvider.getUntranslatedMessages();
|
||||
fakeElement = {
|
||||
setAttribute: sandbox.stub(),
|
||||
removeAttribute: sandbox.stub(),
|
||||
querySelector: sandbox.stub(),
|
||||
addEventListener: sandbox.stub(),
|
||||
};
|
||||
// Share the same element when selecting child nodes
|
||||
fakeElement.querySelector.returns(fakeElement);
|
||||
everyWindowStub = {
|
||||
registerCallback: sandbox.stub(),
|
||||
unregisterCallback: sandbox.stub(),
|
||||
};
|
||||
globals.set("EveryWindow", everyWindowStub);
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
it("should create an instance", () => {
|
||||
assert.ok(instance);
|
||||
});
|
||||
describe("#init", () => {
|
||||
it("should make a messageRequest on init", async () => {
|
||||
sandbox.stub(instance, "messageRequest");
|
||||
const waitForInitialized = sandbox.stub().resolves();
|
||||
|
||||
await instance.init(waitForInitialized, {});
|
||||
assert.calledOnce(instance.messageRequest);
|
||||
assert.calledWithExactly(instance.messageRequest, "toolbarBadgeUpdate");
|
||||
});
|
||||
});
|
||||
describe("messageRequest", () => {
|
||||
let handleMessageRequestStub;
|
||||
beforeEach(() => {
|
||||
handleMessageRequestStub = sandbox.stub().returns(fxaMessage);
|
||||
sandbox
|
||||
.stub(instance, "_handleMessageRequest")
|
||||
.value(handleMessageRequestStub);
|
||||
sandbox.stub(instance, "registerBadgeNotificationListener");
|
||||
});
|
||||
it("should fetch a message with the provided trigger and template", async () => {
|
||||
await instance.messageRequest("trigger");
|
||||
|
||||
assert.calledOnce(handleMessageRequestStub);
|
||||
assert.calledWithExactly(handleMessageRequestStub, {
|
||||
triggerId: "trigger",
|
||||
template: instance.template,
|
||||
});
|
||||
});
|
||||
it("should call addToolbarNotification with browser window and message", async () => {
|
||||
await instance.messageRequest("trigger");
|
||||
|
||||
assert.calledOnce(instance.registerBadgeNotificationListener);
|
||||
assert.calledWithExactly(
|
||||
instance.registerBadgeNotificationListener,
|
||||
fxaMessage
|
||||
);
|
||||
});
|
||||
it("shouldn't do anything if no message is provided", () => {
|
||||
handleMessageRequestStub.returns(null);
|
||||
instance.messageRequest("trigger");
|
||||
|
||||
assert.notCalled(instance.registerBadgeNotificationListener);
|
||||
});
|
||||
});
|
||||
describe("addToolbarNotification", () => {
|
||||
let target;
|
||||
let fakeDocument;
|
||||
beforeEach(() => {
|
||||
fakeDocument = { getElementById: sandbox.stub().returns(fakeElement) };
|
||||
target = { browser: { ownerDocument: fakeDocument } };
|
||||
});
|
||||
it("shouldn't do anything if target element is not found", () => {
|
||||
fakeDocument.getElementById.returns(null);
|
||||
instance.addToolbarNotification(target, fxaMessage);
|
||||
|
||||
assert.notCalled(fakeElement.setAttribute);
|
||||
});
|
||||
it("should target the element specified in the message", () => {
|
||||
instance.addToolbarNotification(target, fxaMessage);
|
||||
|
||||
assert.calledOnce(fakeDocument.getElementById);
|
||||
assert.calledWithExactly(
|
||||
fakeDocument.getElementById,
|
||||
fxaMessage.content.target
|
||||
);
|
||||
});
|
||||
it("should show a notification", () => {
|
||||
instance.addToolbarNotification(target, fxaMessage);
|
||||
|
||||
assert.calledTwice(fakeElement.setAttribute);
|
||||
assert.calledWithExactly(fakeElement.setAttribute, "badged", true);
|
||||
assert.calledWithExactly(fakeElement.setAttribute, "value", "x");
|
||||
});
|
||||
it("should attach a cb on the notification", () => {
|
||||
instance.addToolbarNotification(target, fxaMessage);
|
||||
|
||||
assert.calledOnce(fakeElement.addEventListener);
|
||||
assert.calledWithExactly(
|
||||
fakeElement.addEventListener,
|
||||
"click",
|
||||
instance.removeAllNotifications,
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("registerBadgeNotificationListener", () => {
|
||||
beforeEach(() => {
|
||||
sandbox.stub(instance, "_addImpression").value(fakeAddImpression);
|
||||
sandbox.stub(instance, "addToolbarNotification").returns(fakeElement);
|
||||
sandbox.stub(instance, "removeToolbarNotification");
|
||||
});
|
||||
it("should add an impression for the message", () => {
|
||||
instance.registerBadgeNotificationListener(fxaMessage);
|
||||
|
||||
assert.calledOnce(instance._addImpression);
|
||||
assert.calledWithExactly(instance._addImpression, fxaMessage);
|
||||
});
|
||||
it("should register a callback that adds/removes the notification", () => {
|
||||
instance.registerBadgeNotificationListener(fxaMessage);
|
||||
|
||||
assert.calledOnce(everyWindowStub.registerCallback);
|
||||
assert.calledWithExactly(
|
||||
everyWindowStub.registerCallback,
|
||||
instance.id,
|
||||
sinon.match.func,
|
||||
sinon.match.func
|
||||
);
|
||||
|
||||
const [
|
||||
,
|
||||
initFn,
|
||||
uninitFn,
|
||||
] = everyWindowStub.registerCallback.firstCall.args;
|
||||
|
||||
initFn(window);
|
||||
// Test that it doesn't try to add a second notification
|
||||
initFn(window);
|
||||
|
||||
assert.calledOnce(instance.addToolbarNotification);
|
||||
assert.calledWithExactly(
|
||||
instance.addToolbarNotification,
|
||||
window,
|
||||
fxaMessage
|
||||
);
|
||||
|
||||
uninitFn(window);
|
||||
|
||||
assert.calledOnce(instance.removeToolbarNotification);
|
||||
assert.calledWithExactly(instance.removeToolbarNotification, fakeElement);
|
||||
});
|
||||
});
|
||||
describe("removeToolbarNotification", () => {
|
||||
it("should remove the notification", () => {
|
||||
instance.removeToolbarNotification(fakeElement);
|
||||
|
||||
assert.calledTwice(fakeElement.removeAttribute);
|
||||
assert.calledWithExactly(fakeElement.removeAttribute, "badged");
|
||||
});
|
||||
});
|
||||
describe("removeAllNotifications", () => {
|
||||
let blockMessageByIdStub;
|
||||
beforeEach(() => {
|
||||
blockMessageByIdStub = sandbox.stub();
|
||||
sandbox.stub(instance, "_blockMessageById").value(blockMessageByIdStub);
|
||||
instance.state = { notification: { id: fxaMessage.id } };
|
||||
});
|
||||
it("should call to block the message", () => {
|
||||
instance.removeAllNotifications();
|
||||
|
||||
assert.calledOnce(blockMessageByIdStub);
|
||||
assert.calledWithExactly(blockMessageByIdStub, fxaMessage.id);
|
||||
});
|
||||
it("should remove the window listener", () => {
|
||||
instance.removeAllNotifications();
|
||||
|
||||
assert.calledOnce(everyWindowStub.unregisterCallback);
|
||||
assert.calledWithExactly(everyWindowStub.unregisterCallback, instance.id);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -282,7 +282,11 @@ const TEST_GLOBAL = {
|
|||
createNullPrincipal() {},
|
||||
getSystemPrincipal() {},
|
||||
},
|
||||
wm: { getMostRecentWindow: () => window, getEnumerator: () => [] },
|
||||
wm: {
|
||||
getMostRecentWindow: () => window,
|
||||
getMostRecentBrowserWindow: () => window,
|
||||
getEnumerator: () => [],
|
||||
},
|
||||
ww: { registerNotification() {}, unregisterNotification() {} },
|
||||
appinfo: { appBuildID: "20180710100040" },
|
||||
},
|
||||
|
|
Загрузка…
Ссылка в новой задаче