// eslint-disable-next-line max-statements
describe("ASRouter", () => {
let Router;
let globals;
let sandbox;
let initParams;
let messageBlockList;
let providerBlockList;
let messageImpressions;
let groupImpressions;
let previousSessionEnd;
let fetchStub;
let clock;
let fakeAttributionCode;
let fakeTargetingContext;
let FakeToolbarBadgeHub;
let FakeMomentsPageHub;
let ASRouterTargeting;
let gBrowser;
let screenImpressions;
function setMessageProviderPref(value) {
sandbox.stub(ASRouterPreferences, "providers").get(() => value);
}
async function createRouterAndInit(providers = FAKE_PROVIDERS) {
setMessageProviderPref(providers); // `.freeze` to catch any attempts at modifying the object
Router = new _ASRouter(Object.freeze(FAKE_LOCAL_PROVIDERS));
await initASRouter(Router);
}
evalWithDefault(expr) { return fakeTargetingContext.evalWithDefault(expr);
}
},
RemoteL10n: { // This is just a subset of supported locales that happen to be used in // the test.
isLocaleSupported: locale => ["en-US", "ja-JP-mac"].includes(locale),
},
});
await createRouterAndInit();
});
afterEach(() => {
Router.uninit();
ASRouterPreferences.uninit();
sandbox.restore();
globals.restore();
});
describe(".state", () => {
it("should throw if an attempt to set .state was made", () => { assert.throws(() => {
Router.state = {};
});
});
});
describe("#init", () => {
it("should only be called once", async () => {
Router = new _ASRouter();
let state = await initASRouter(Router);
assert.equal(state, Router.state);
assert.isNull(await Router.init({}));
});
it("should only be called once", async () => {
Router = new _ASRouter();
initASRouter(Router);
let secondCall = await Router.init({});
assert.isNull(
secondCall, "Should not init twice, it should exit early with null"
);
});
it("should set state.messageBlockList to the block list in persistent storage", async () => {
messageBlockList = ["foo"];
Router = new _ASRouter();
await initASRouter(Router);
assert.deepEqual(Router.state.messageBlockList, ["foo"]);
});
it("should initialize all the hub providers", async () => { // ASRouter init called in `beforeEach` block above
assert.calledWithExactly(
FakeMomentsPageHub.init,
Router.waitForInitialized,
{
handleMessageRequest: Router.handleMessageRequest,
addImpression: Router.addImpression,
blockMessageById: Router.blockMessageById,
sendTelemetry: Router.sendTelemetry,
}
);
});
it("should set state.messageImpressions to the messageImpressions object in persistent storage", async () => { // Note that messageImpressions are only kept if a message exists in router and has a .frequency property, // otherwise they will be cleaned up by .cleanupImpressions() const testMessage = { id: "foo", frequency: { lifetimeCap: 10 } };
messageImpressions = { foo: [0, 1, 2] };
setMessageProviderPref([
{ id: "onboarding", type: "local", messages: [testMessage] },
]);
Router = new _ASRouter();
await initASRouter(Router);
assert.deepEqual(Router.state.messageImpressions, messageImpressions);
});
it("should set state.screenImpressions to the screenImpressions object in persistent storage", async () => {
screenImpressions = { test: 123 };
Router = new _ASRouter();
await initASRouter(Router);
assert.deepEqual(Router.state.screenImpressions, screenImpressions);
});
it("should clear impressions for groups that are not active", async () => {
groupImpressions = { foo: [0, 1, 2] };
Router = new _ASRouter();
await initASRouter(Router);
assert.calledOnce(global.Services.prefs.addObserver); assert.calledWithExactly(
global.Services.prefs.addObserver,
USE_REMOTE_L10N_PREF,
Router
);
});
describe("lazily loading local test providers", () => {
let justIdAndContent = ({ id, content }) => ({ id, content });
afterEach(() => Router.uninit());
it("should add the local test providers on init if devtools are enabled", async () => {
sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => true);
await createRouterAndInit();
assert.property(Router._localProviders, "PanelTestProvider");
});
it("should not add the local test providers on init if devtools are disabled", async () => {
sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => false);
await createRouterAndInit();
assert.notProperty(Router._localProviders, "PanelTestProvider");
});
it("should flatten experiment translated messages from local test providers if devtools are enabled...", async () => {
sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => true);
describe("setState", () => {
it("should broadcast a message to update the admin tool on a state change if the asrouter.devtoolsEnabled pref is", async () => {
sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => true);
sandbox.stub(Router, "getTargetingParameters").resolves({}); const state = await Router.setState({ foo: 123 });
assert.calledOnce(initParams.updateAdminState); assert.deepEqual(state.providerPrefs, ASRouterPreferences.providers); assert.deepEqual(
state.userPrefs,
ASRouterPreferences.getAllUserPreferences()
); assert.deepEqual(state.targetingParameters, {}); assert.deepEqual(state.errors, Router.errors);
});
it("should not send a message on a state change asrouter.devtoolsEnabled pref is on", async () => {
sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => false);
await Router.setState({ foo: 123 });
describe("#loadMessagesFromAllProviders", () => { function assertRouterContainsMessages(messages) { const messageIdsInRouter = Router.state.messages.map(m => m.id); for (const message of messages) { assert.include(messageIdsInRouter, message.id);
}
}
it("should not trigger an update if not enough time has passed for a provider", async () => {
await createRouterAndInit([
{
id: "remotey",
type: "remote",
enabled: true,
url: "http://fake.com/endpoint",
updateCycleInMs: 300,
},
]);
const previousState = Router.state;
// Since we've previously gotten messages during init and we haven't advanced our fake timer, // no updates should be triggered.
await Router.loadMessagesFromAllProviders(); assert.deepEqual(Router.state, previousState);
});
it("should not trigger an update if we only have local providers", async () => {
await createRouterAndInit([
{
id: "foo",
type: "local",
enabled: true,
messages: FAKE_LOCAL_MESSAGES,
},
]);
// These are the new messages
assertRouterContainsMessages(NEW_MESSAGES); // These are the local messages that should not have been deleted
assertRouterContainsMessages(FAKE_LOCAL_MESSAGES);
});
it("should parse the triggers in the messages and register the trigger listeners", async () => {
sandbox.spy(
ASRouterTriggerListeners.get("openURL"), "init"
); /* eslint-disable object-property-newline */
describe("#_updateMessageProviders", () => {
it("should correctly replace %STARTPAGE_VERSION% in remote provider urls", async () => { // If this test fails, you need to update the constant STARTPAGE_VERSION in // ASRouter.sys.mjs to match the `version` property of provider-response-schema.json const expectedStartpageVersion = ProviderResponseSchema.version; const provider = {
id: "foo",
enabled: true,
type: "remote",
url: "https://www.mozilla.org/%STARTPAGE_VERSION%/",
};
setMessageProviderPref([provider]);
await Router._updateMessageProviders(); assert.equal(
Router.state.providers[0].url,
`https://www.mozilla.org/${parseInt(expectedStartpageVersion, 10)}/`
);
});
it("should replace other params in remote provider urls by calling Services.urlFormater.formatURL", async () => { const url = "https://www.example.com/"; const replacedUrl = "https://www.foo.bar/"; const stub = sandbox
.stub(global.Services.urlFormatter, "formatURL")
.withArgs(url)
.returns(replacedUrl); const provider = { id: "foo", enabled: true, type: "remote", url };
setMessageProviderPref([provider]);
await Router._updateMessageProviders(); assert.calledOnce(stub); assert.calledWithExactly(stub, url); assert.equal(Router.state.providers[0].url, replacedUrl);
});
it("should only add the providers that are enabled", async () => { const providers = [
{
id: "foo",
enabled: false,
type: "remote",
url: "https://www.foo.com/",
},
{
id: "bar",
enabled: true,
type: "remote",
url: "https://www.bar.com/",
},
];
setMessageProviderPref(providers);
await Router._updateMessageProviders(); assert.equal(Router.state.providers.length, 1); assert.equal(Router.state.providers[0].id, providers[1].id);
});
});
describe("#handleMessageRequest", () => {
beforeEach(async () => {
await Router.setState(() => ({
providers: [{ id: "cfr" }, { id: "badge" }],
}));
});
it("should not return a blocked message", async () => { // Block all messages except the first
await Router.setState(() => ({
messages: [
{ id: "foo", provider: "cfr", groups: ["cfr"] },
{ id: "bar", provider: "cfr", groups: ["cfr"] },
],
messageBlockList: ["foo"],
}));
await Router.handleMessageRequest({
provider: "cfr",
}); assert.calledWithMatch(ASRouterTargeting.findMatchingMessage, {
messages: [{ id: "bar", provider: "cfr", groups: ["cfr"] }],
});
});
it("should not return a message from a disabled group", async () => {
ASRouterTargeting.findMatchingMessage.callsFake(
({ messages }) => messages[0]
); // Block all messages except the first
await Router.setState(() => ({
messages: [
{ id: "foo", provider: "cfr", groups: ["cfr"] },
{ id: "bar", provider: "cfr", groups: ["cfr"] },
],
groups: [{ id: "cfr", enabled: false }],
})); const result = await Router.handleMessageRequest({
provider: "cfr",
}); assert.isNull(result);
});
it("should not return a message from a blocked campaign", async () => { // Block all messages except the first
await Router.setState(() => ({
messages: [
{
id: "foo",
provider: "cfr",
campaign: "foocampaign",
groups: ["cfr"],
},
{ id: "bar", provider: "cfr", groups: ["cfr"] },
],
messageBlockList: ["foocampaign"],
}));
await Router.handleMessageRequest({
provider: "cfr",
}); assert.calledWithMatch(ASRouterTargeting.findMatchingMessage, {
messages: [{ id: "bar", provider: "cfr", groups: ["cfr"] }],
});
});
it("should not return a message excluded by the provider", async () => { // There are only two providers; block the FAKE_LOCAL_PROVIDER, leaving // only FAKE_REMOTE_PROVIDER unblocked, which provides only one message
await Router.setState(() => ({
providers: [{ id: "cfr", exclude: ["foo"] }],
}));
describe("#uninit", () => {
it("should unregister the trigger listeners", () => { for (const listener of ASRouterTriggerListeners.values()) {
sandbox.spy(listener, "uninit");
}
Router.uninit();
for (const listener of ASRouterTriggerListeners.values()) { assert.calledOnce(listener.uninit);
}
});
it("should set .dispatchCFRAction to null", () => {
Router.uninit(); assert.isNull(Router.dispatchCFRAction); assert.isNull(Router.clearChildMessages); assert.isNull(Router.sendTelemetry);
});
it("should save previousSessionEnd", () => {
Router.uninit();
assert.calledOnce(Router._storage.set); assert.calledWithExactly(
Router._storage.set, "previousSessionEnd",
sinon.match.number
);
});
it("should remove the observer for `intl:app-locales-changed`", () => {
sandbox.spy(global.Services.obs, "removeObserver");
Router.uninit();
assert.calledWithExactly(
global.Services.obs.removeObserver,
Router._onLocaleChanged, "intl:app-locales-changed"
);
});
it("should remove the pref observer for `USE_REMOTE_L10N_PREF`", async () => {
sandbox.spy(global.Services.prefs, "removeObserver");
Router.uninit();
// Grab the last call as #uninit() also involves multiple calls of `Services.prefs.removeObserver`. const call = global.Services.prefs.removeObserver.lastCall; assert.calledWithExactly(call, USE_REMOTE_L10N_PREF, Router);
});
});
describe("#setMessageById", async () => {
it("should send an empty message if provided id did not resolve to a message", async () => {
let response = await Router.setMessageById({ id: -1 }, true, {}); assert.deepEqual(response.message, {});
});
});
describe("#isUnblockedMessage", () => {
it("should block a message if the group is blocked", async () => { const msg = { id: "msg1", groups: ["foo"], provider: "unit-test" };
await Router.setState({
groups: [{ id: "foo", enabled: false }],
messages: [msg],
providers: [{ id: "unit-test" }],
}); assert.isFalse(Router.isUnblockedMessage(msg));
describe("#blockMessageById", () => {
it("should add the id to the messageBlockList", async () => {
await Router.blockMessageById("foo"); assert.isTrue(Router.state.messageBlockList.includes("foo"));
});
it("should add the campaign to the messageBlockList instead of id if .campaign is specified and not select messages of that campaign again", async () => {
await Router.setState({
messages: [
{ id: "1", campaign: "foocampaign" },
{ id: "2", campaign: "foocampaign" },
],
});
await Router.blockMessageById("1");
assert.isTrue(Router.state.messageBlockList.includes("foocampaign")); assert.isEmpty(Router.state.messages.filter(Router.isUnblockedMessage));
});
it("should be able to add multiple items to the messageBlockList", async () => {
await Router.blockMessageById(FAKE_BUNDLE.map(b => b.id)); assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id)); assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
});
it("should save the messageBlockList", async () => {
await Router.blockMessageById(FAKE_BUNDLE.map(b => b.id)); assert.calledWithExactly(Router._storage.set, "messageBlockList", [
FAKE_BUNDLE[0].id,
FAKE_BUNDLE[1].id,
]);
});
});
describe("#unblockMessageById", () => {
it("should remove the id from the messageBlockList", async () => {
await Router.blockMessageById("foo"); assert.isTrue(Router.state.messageBlockList.includes("foo"));
await Router.unblockMessageById("foo"); assert.isFalse(Router.state.messageBlockList.includes("foo"));
});
it("should remove the campaign from the messageBlockList if it is defined", async () => {
await Router.setState({ messages: [{ id: "1", campaign: "foo" }] });
await Router.blockMessageById("1"); assert.isTrue(
Router.state.messageBlockList.includes("foo"), "blocklist has campaign id"
);
await Router.unblockMessageById("1"); assert.isFalse(
Router.state.messageBlockList.includes("foo"), "campaign id removed from blocklist"
);
});
it("should save the messageBlockList", async () => {
await Router.unblockMessageById("foo"); assert.calledWithExactly(Router._storage.set, "messageBlockList", []);
});
});
describe("#routeCFRMessage", () => {
it("should allow for echoing back message modifications", () => { const message = { somekey: "some value" }; const data = { content: message }; const browser = {};
let msg = Router.routeCFRMessage(data.content, browser, data, false); assert.deepEqual(msg.message, message);
});
it("should call CFRPageActions.forceRecommendation if the template is cfr_action and force is true", async () => {
sandbox.stub(CFRPageActions, "forceRecommendation"); const testMessage = { id: "foo", template: "cfr_doorhanger" };
await Router.setState({ messages: [testMessage] });
Router.routeCFRMessage(testMessage, {}, null, true);
assert.calledOnce(CFRPageActions.forceRecommendation);
});
it("should call CFRPageActions.addRecommendation if the template is cfr_action and force is false", async () => {
sandbox.stub(CFRPageActions, "addRecommendation"); const testMessage = { id: "foo", template: "cfr_doorhanger" };
await Router.setState({ messages: [testMessage] });
Router.routeCFRMessage(testMessage, {}, {}, false); assert.calledOnce(CFRPageActions.addRecommendation);
});
});
describe("#updateTargetingParameters", () => {
it("should return an object containing the whole state", async () => {
sandbox.stub(Router, "getTargetingParameters").resolves({});
let msg = await Router.updateTargetingParameters();
let expected = Object.assign({}, Router.state, {
providerPrefs: ASRouterPreferences.providers,
userPrefs: ASRouterPreferences.getAllUserPreferences(),
targetingParameters: {},
errors: Router.errors,
devtoolsEnabled: ASRouterPreferences.devtoolsEnabled,
});
assert.deepEqual(msg, expected);
});
});
describe("#reachEvent", () => {
let experimentAPIStub;
let featureIds = ["cfr", "moments-page", "infobar", "spotlight"];
beforeEach(() => {
let getExperimentMetaDataStub = sandbox.stub();
let getAllBranchesStub = sandbox.stub();
featureIds.forEach(feature => {
global.NimbusFeatures[feature].getAllVariables.returns({
id: `message-${feature}`,
});
getExperimentMetaDataStub.withArgs({ featureId: feature }).returns({
slug: `slug-${feature}`,
branch: {
slug: `branch-${feature}`,
},
});
getAllBranchesStub.withArgs(`slug-${feature}`).resolves([
{
slug: `other-branch-${feature}`,
[feature]: { value: { trigger: "unit-test" } },
},
]);
});
experimentAPIStub = {
getExperimentMetaData: getExperimentMetaDataStub,
getAllBranches: getAllBranchesStub,
};
globals.set("ExperimentAPI", experimentAPIStub);
});
afterEach(() => {
sandbox.restore();
});
it("should tag `forReachEvent` for all the expected message types", async () => { // This should match the `providers.messaging-experiments`
let response = await MessageLoaderUtils.loadMessagesForProvider({
type: "remote-experiments",
featureIds,
});
describe("#isBelowFrequencyCaps", () => {
it("should call #_isBelowItemFrequencyCap for the message and for the provider with the correct impressions and arguments", async () => {
sinon.spy(Router, "_isBelowItemFrequencyCap");
const MAX_MESSAGE_LIFETIME_CAP = 100; // Defined in ASRouter const fooMessageImpressions = [0, 1]; const barGroupImpressions = [0, 1, 2];
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.