// 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];
describe("#_isBelowItemFrequencyCap", () => {
it("should return false if the # of impressions exceeds the maxLifetimeCap", () => { const item = { id: "foo", frequency: { lifetime: 5 } }; const impressions = [0, 1]; const maxLifetimeCap = 1; const result = Router._isBelowItemFrequencyCap(
item,
impressions,
maxLifetimeCap
); assert.isFalse(result);
});
describe("lifetime frequency caps", () => {
it("should return true if .frequency is not defined on the item", () => { const item = { id: "foo" }; const impressions = [0, 1]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isTrue(result);
});
it("should return true if there are no impressions", () => { const item = {
id: "foo",
frequency: {
lifetime: 10,
custom: [{ period: ONE_DAY_IN_MS, cap: 2 }],
},
}; const impressions = []; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isTrue(result);
});
it("should return true if the # of impressions is less than .frequency.lifetime of the item", () => { const item = { id: "foo", frequency: { lifetime: 3 } }; const impressions = [0, 1]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isTrue(result);
});
it("should return false if the # of impressions is equal to .frequency.lifetime of the item", async () => { const item = { id: "foo", frequency: { lifetime: 3 } }; const impressions = [0, 1, 2]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isFalse(result);
});
it("should return false if the # of impressions is greater than .frequency.lifetime of the item", async () => { const item = { id: "foo", frequency: { lifetime: 3 } }; const impressions = [0, 1, 2, 3]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isFalse(result);
});
});
describe("custom frequency caps", () => {
it("should return true if impressions in the time period < the cap and total impressions < the lifetime cap", () => {
clock.tick(ONE_DAY_IN_MS + 10); const item = {
id: "foo",
frequency: {
custom: [{ period: ONE_DAY_IN_MS, cap: 2 }],
lifetime: 3,
},
}; const impressions = [0, ONE_DAY_IN_MS + 1]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isTrue(result);
});
it("should return false if impressions in the time period > the cap and total impressions < the lifetime cap", () => {
clock.tick(200); const item = {
id: "msg1",
frequency: { custom: [{ period: 100, cap: 2 }], lifetime: 3 },
}; const impressions = [0, 160, 161]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isFalse(result);
});
it("should return false if impressions in one of the time periods > the cap and total impressions < the lifetime cap", () => {
clock.tick(ONE_DAY_IN_MS + 200); const itemTrue = {
id: "msg2",
frequency: { custom: [{ period: 100, cap: 2 }] },
}; const itemFalse = {
id: "msg1",
frequency: {
custom: [
{ period: 100, cap: 2 },
{ period: ONE_DAY_IN_MS, cap: 3 },
],
},
}; const impressions = [
0,
ONE_DAY_IN_MS + 160,
ONE_DAY_IN_MS - 100,
ONE_DAY_IN_MS - 200,
]; assert.isTrue(Router._isBelowItemFrequencyCap(itemTrue, impressions)); assert.isFalse(
Router._isBelowItemFrequencyCap(itemFalse, impressions)
);
});
it("should return false if impressions in the time period < the cap and total impressions > the lifetime cap", () => {
clock.tick(ONE_DAY_IN_MS + 10); const item = {
id: "msg1",
frequency: {
custom: [{ period: ONE_DAY_IN_MS, cap: 2 }],
lifetime: 3,
},
}; const impressions = [0, 1, 2, 3, ONE_DAY_IN_MS + 1]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isFalse(result);
});
it("should return true if daily impressions < the daily cap and there is no lifetime cap", () => {
clock.tick(ONE_DAY_IN_MS + 10); const item = {
id: "msg1",
frequency: { custom: [{ period: ONE_DAY_IN_MS, cap: 2 }] },
}; const impressions = [0, 1, 2, 3, ONE_DAY_IN_MS + 1]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isTrue(result);
});
it("should return false if daily impressions > the daily cap and there is no lifetime cap", () => {
clock.tick(ONE_DAY_IN_MS + 10); const item = {
id: "msg1",
frequency: { custom: [{ period: ONE_DAY_IN_MS, cap: 2 }] },
}; const impressions = [
0,
1,
2,
3,
ONE_DAY_IN_MS + 1,
ONE_DAY_IN_MS + 2,
ONE_DAY_IN_MS + 3,
]; const result = Router._isBelowItemFrequencyCap(item, impressions); assert.isFalse(result);
});
});
});
describe("#getLongestPeriod", () => {
it("should return the period if there is only one definition", () => { const message = {
id: "foo",
frequency: { custom: [{ period: 200, cap: 2 }] },
}; assert.equal(Router.getLongestPeriod(message), 200);
});
it("should return the longest period if there are more than one definitions", () => { const message = {
id: "foo",
frequency: {
custom: [
{ period: 1000, cap: 3 },
{ period: ONE_DAY_IN_MS, cap: 5 },
{ period: 100, cap: 2 },
],
},
}; assert.equal(Router.getLongestPeriod(message), ONE_DAY_IN_MS);
});
it("should return null if there are is no .frequency", () => { const message = { id: "foo" }; assert.isNull(Router.getLongestPeriod(message));
});
it("should return null if there are is no .frequency.custom", () => { const message = { id: "foo", frequency: { lifetime: 10 } }; assert.isNull(Router.getLongestPeriod(message));
});
});
describe("cleanup on init", () => {
it("should clear messageImpressions for messages which do not exist in state.messages", async () => { const messages = [{ id: "foo", frequency: { lifetime: 10 } }];
messageImpressions = { foo: [0], bar: [0, 1] }; // Impressions for "bar" should be removed since that id does not exist in messages const result = { foo: [0] };
await createRouterAndInit([
{ id: "onboarding", type: "local", messages, enabled: true },
]); assert.calledWith(Router._storage.set, "messageImpressions", result); assert.deepEqual(Router.state.messageImpressions, result);
});
it("should clear messageImpressions older than the period if no lifetime impression cap is included", async () => { const CURRENT_TIME = ONE_DAY_IN_MS * 2;
clock.tick(CURRENT_TIME); const messages = [
{
id: "foo",
frequency: { custom: [{ period: ONE_DAY_IN_MS, cap: 5 }] },
},
];
messageImpressions = { foo: [0, 1, CURRENT_TIME - 10] }; // Only 0 and 1 are more than 24 hours before CURRENT_TIME const result = { foo: [CURRENT_TIME - 10] };
await createRouterAndInit([
{ id: "onboarding", type: "local", messages, enabled: true },
]); assert.calledWith(Router._storage.set, "messageImpressions", result); assert.deepEqual(Router.state.messageImpressions, result);
});
it("should clear messageImpressions older than the longest period if no lifetime impression cap is included", async () => { const CURRENT_TIME = ONE_DAY_IN_MS * 2;
clock.tick(CURRENT_TIME); const messages = [
{
id: "foo",
frequency: {
custom: [
{ period: ONE_DAY_IN_MS, cap: 5 },
{ period: 100, cap: 2 },
],
},
},
];
messageImpressions = { foo: [0, 1, CURRENT_TIME - 10] }; // Only 0 and 1 are more than 24 hours before CURRENT_TIME const result = { foo: [CURRENT_TIME - 10] };
await createRouterAndInit([
{ id: "onboarding", type: "local", messages, enabled: true },
]); assert.calledWith(Router._storage.set, "messageImpressions", result); assert.deepEqual(Router.state.messageImpressions, result);
});
it("should clear messageImpressions if they are not properly formatted", async () => { const messages = [{ id: "foo", frequency: { lifetime: 10 } }]; // this is impromperly formatted since messageImpressions are supposed to be an array
messageImpressions = { foo: 0 }; const result = {};
// Nedds to match the `featureIds` value to return an enrollment // for that feature
global.NimbusFeatures.cfr.getAllVariables.returns(
enrollment.branch.cfr.value
);
global.ExperimentAPI.getExperimentMetaData.returns({
slug: enrollment.slug,
active: true,
branch: { slug: enrollment.branch.slug },
});
global.ExperimentAPI.getAllBranches.resolves([
enrollment.branch,
{
slug: "branch02",
cfr: {
featureId: "cfr",
value: { id: "id02", trigger: { id: "openURL" } },
},
},
{ // This branch should not be loaded as it doesn't have the trigger
slug: "branch03",
cfr: {
featureId: "cfr",
value: { id: "id03" },
},
},
]);
const result = await MessageLoaderUtils.loadMessagesForProvider(args);
assert.equal(result.messages.length, 1); assert.equal(result.messages[0].id, "id02"); assert.equal(result.messages[0].experimentSlug, "exp01"); assert.equal(result.messages[0].branchSlug, "branch02"); assert.deepEqual(result.messages[0].forReachEvent, {
sent: false,
group: "cfr",
});
});
});
describe("#_remoteSettingsLoader", () => {
let provider;
let spy;
beforeEach(() => {
provider = {
id: "cfr",
collection: "cfr",
};
sandbox
.stub(MessageLoaderUtils, "_getRemoteSettingsMessages")
.resolves([{ id: "message_1" }]);
spy = sandbox.spy();
global.Downloader.prototype.downloadToDisk = spy;
});
it("should be called with the expected dir path", async () => { const dlSpy = sandbox.spy(global, "Downloader");
// The original messages now have zero impressions assert.isEmpty(Router.state.messageImpressions["1"]); assert.isEmpty(Router.state.messageImpressions["2"]); // A new impression array was added for the new message assert.equal(Router.state.messageImpressions["3"].length, 3); assert.calledWithExactly(Router._storage.set, "messageImpressions", {
1: [],
2: [],
3: [0, 1, 2],
});
});
});
});
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.38 Sekunden
(vorverarbeitet am 2026-04-28)
¤
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.