MomentsPageHub.test.js (10756B)
1 import { GlobalOverrider } from "tests/unit/utils"; 2 import { PanelTestProvider } from "modules/PanelTestProvider.sys.mjs"; 3 import { _MomentsPageHub } from "modules/MomentsPageHub.sys.mjs"; 4 const HOMEPAGE_OVERRIDE_PREF = "browser.startup.homepage_override.once"; 5 6 describe("MomentsPageHub", () => { 7 let globals; 8 let sandbox; 9 let instance; 10 let handleMessageRequestStub; 11 let addImpressionStub; 12 let blockMessageByIdStub; 13 let sendTelemetryStub; 14 let getStringPrefStub; 15 let setStringPrefStub; 16 let setIntervalStub; 17 let clearIntervalStub; 18 19 beforeEach(async () => { 20 globals = new GlobalOverrider(); 21 sandbox = sinon.createSandbox(); 22 instance = new _MomentsPageHub(); 23 const messages = (await PanelTestProvider.getMessages()).filter( 24 ({ template }) => template === "update_action" 25 ); 26 handleMessageRequestStub = sandbox.stub().resolves(messages); 27 addImpressionStub = sandbox.stub(); 28 blockMessageByIdStub = sandbox.stub(); 29 getStringPrefStub = sandbox.stub(); 30 setStringPrefStub = sandbox.stub(); 31 setIntervalStub = sandbox.stub(); 32 clearIntervalStub = sandbox.stub(); 33 sendTelemetryStub = sandbox.stub(); 34 globals.set({ 35 setInterval: setIntervalStub, 36 clearInterval: clearIntervalStub, 37 Services: { 38 prefs: { 39 getStringPref: getStringPrefStub, 40 setStringPref: setStringPrefStub, 41 }, 42 }, 43 Glean: { 44 messagingExperiments: { 45 reachMomentsPage: { 46 record: () => {}, 47 }, 48 }, 49 messagingSystem: { 50 messageRequestTime: { 51 start() {}, 52 stopAndAccumulate() {}, 53 }, 54 }, 55 }, 56 }); 57 }); 58 59 afterEach(() => { 60 sandbox.restore(); 61 globals.restore(); 62 }); 63 64 it("should create an instance", async () => { 65 setIntervalStub.returns(42); 66 assert.ok(instance); 67 await instance.init(Promise.resolve(), { 68 handleMessageRequest: handleMessageRequestStub, 69 addImpression: addImpressionStub, 70 blockMessageById: blockMessageByIdStub, 71 }); 72 assert.equal(instance.state._intervalId, 42); 73 }); 74 75 it("should init only once", async () => { 76 assert.notCalled(handleMessageRequestStub); 77 78 await instance.init(Promise.resolve(), { 79 handleMessageRequest: handleMessageRequestStub, 80 addImpression: addImpressionStub, 81 blockMessageById: blockMessageByIdStub, 82 }); 83 await instance.init(Promise.resolve(), { 84 handleMessageRequest: handleMessageRequestStub, 85 addImpression: addImpressionStub, 86 blockMessageById: blockMessageByIdStub, 87 }); 88 89 assert.calledOnce(handleMessageRequestStub); 90 91 instance.uninit(); 92 93 await instance.init(Promise.resolve(), { 94 handleMessageRequest: handleMessageRequestStub, 95 addImpression: addImpressionStub, 96 blockMessageById: blockMessageByIdStub, 97 }); 98 99 assert.calledTwice(handleMessageRequestStub); 100 }); 101 102 it("should uninit the instance", () => { 103 instance.uninit(); 104 assert.calledOnce(clearIntervalStub); 105 }); 106 107 it("should setInterval for `checkHomepageOverridePref`", async () => { 108 await instance.init(sandbox.stub().resolves(), {}); 109 sandbox.stub(instance, "checkHomepageOverridePref"); 110 111 assert.calledOnce(setIntervalStub); 112 assert.calledWithExactly(setIntervalStub, sinon.match.func, 5 * 60 * 1000); 113 114 assert.notCalled(instance.checkHomepageOverridePref); 115 const [cb] = setIntervalStub.firstCall.args; 116 117 cb(); 118 119 assert.calledOnce(instance.checkHomepageOverridePref); 120 }); 121 122 describe("#messageRequest", () => { 123 beforeEach(async () => { 124 await instance.init(Promise.resolve(), { 125 handleMessageRequest: handleMessageRequestStub, 126 addImpression: addImpressionStub, 127 blockMessageById: blockMessageByIdStub, 128 sendTelemetry: sendTelemetryStub, 129 }); 130 }); 131 afterEach(() => { 132 instance.uninit(); 133 }); 134 it("should fetch a message with the provided trigger and template", async () => { 135 await instance.messageRequest({ 136 triggerId: "trigger", 137 template: "template", 138 }); 139 140 assert.calledTwice(handleMessageRequestStub); 141 assert.calledWithExactly(handleMessageRequestStub, { 142 triggerId: "trigger", 143 template: "template", 144 returnAll: true, 145 }); 146 }); 147 it("shouldn't do anything if no message is provided", async () => { 148 // Reset the call from `instance.init` 149 setStringPrefStub.reset(); 150 handleMessageRequestStub.resolves([]); 151 await instance.messageRequest({ triggerId: "trigger" }); 152 153 assert.notCalled(setStringPrefStub); 154 }); 155 it("should record a message request time", async () => { 156 const fakeTimerId = 42; 157 const start = sandbox 158 .stub(global.Glean.messagingSystem.messageRequestTime, "start") 159 .returns(fakeTimerId); 160 const stopAndAccumulate = sandbox.stub( 161 global.Glean.messagingSystem.messageRequestTime, 162 "stopAndAccumulate" 163 ); 164 165 await instance.messageRequest({ triggerId: "trigger" }); 166 167 assert.calledOnce(start); 168 assert.calledWithExactly(start); 169 assert.calledOnce(stopAndAccumulate); 170 assert.calledWithExactly(stopAndAccumulate, fakeTimerId); 171 }); 172 it("should record Reach event for the Moments page experiment", async () => { 173 const momentsMessages = (await PanelTestProvider.getMessages()).filter( 174 ({ template }) => template === "update_action" 175 ); 176 const messages = [ 177 { 178 forReachEvent: { sent: false }, 179 experimentSlug: "foo", 180 branchSlug: "bar", 181 }, 182 ...momentsMessages, 183 ]; 184 handleMessageRequestStub.resolves(messages); 185 sandbox.spy(global.Glean.messagingExperiments.reachMomentsPage, "record"); 186 sandbox.spy(instance, "executeAction"); 187 188 await instance.messageRequest({ triggerId: "trigger" }); 189 190 assert.calledOnce( 191 global.Glean.messagingExperiments.reachMomentsPage.record 192 ); 193 assert.calledOnce(instance.executeAction); 194 }); 195 it("should not record the Reach event if it's already sent", async () => { 196 const messages = [ 197 { 198 forReachEvent: { sent: true }, 199 experimentSlug: "foo", 200 branchSlug: "bar", 201 }, 202 ]; 203 handleMessageRequestStub.resolves(messages); 204 sandbox.spy(global.Glean.messagingExperiments.reachMomentsPage, "record"); 205 206 await instance.messageRequest({ triggerId: "trigger" }); 207 208 assert.notCalled( 209 global.Glean.messagingExperiments.reachMomentsPage.record 210 ); 211 }); 212 it("should not trigger the action if it's only for the Reach event", async () => { 213 const messages = [ 214 { 215 forReachEvent: { sent: false }, 216 experimentSlug: "foo", 217 branchSlug: "bar", 218 }, 219 ]; 220 handleMessageRequestStub.resolves(messages); 221 sandbox.spy(global.Glean.messagingExperiments.reachMomentsPage, "record"); 222 sandbox.spy(instance, "executeAction"); 223 224 await instance.messageRequest({ triggerId: "trigger" }); 225 226 assert.calledOnce( 227 global.Glean.messagingExperiments.reachMomentsPage.record 228 ); 229 assert.notCalled(instance.executeAction); 230 }); 231 }); 232 describe("executeAction", () => { 233 beforeEach(async () => { 234 blockMessageByIdStub = sandbox.stub(); 235 await instance.init(sandbox.stub().resolves(), { 236 addImpression: addImpressionStub, 237 blockMessageById: blockMessageByIdStub, 238 sendTelemetry: sendTelemetryStub, 239 }); 240 }); 241 it("should set HOMEPAGE_OVERRIDE_PREF on `moments-wnp` action", async () => { 242 const [msg] = await handleMessageRequestStub(); 243 sandbox.useFakeTimers(); 244 instance.executeAction(msg); 245 246 assert.calledOnce(setStringPrefStub); 247 assert.calledWithExactly( 248 setStringPrefStub, 249 HOMEPAGE_OVERRIDE_PREF, 250 JSON.stringify({ 251 message_id: msg.id, 252 url: msg.content.action.data.url, 253 expire: instance.getExpirationDate( 254 msg.content.action.data.expireDelta 255 ), 256 }) 257 ); 258 }); 259 it("should block after taking the action", async () => { 260 const [msg] = await handleMessageRequestStub(); 261 instance.executeAction(msg); 262 263 assert.calledOnce(blockMessageByIdStub); 264 assert.calledWithExactly(blockMessageByIdStub, msg.id); 265 }); 266 it("should compute expire based on expireDelta", async () => { 267 sandbox.spy(instance, "getExpirationDate"); 268 269 const [msg] = await handleMessageRequestStub(); 270 instance.executeAction(msg); 271 272 assert.calledOnce(instance.getExpirationDate); 273 assert.calledWithExactly( 274 instance.getExpirationDate, 275 msg.content.action.data.expireDelta 276 ); 277 }); 278 it("should compute expire based on expireDelta", async () => { 279 sandbox.spy(instance, "getExpirationDate"); 280 281 const [msg] = await handleMessageRequestStub(); 282 const msgWithExpire = { 283 ...msg, 284 content: { 285 ...msg.content, 286 action: { 287 ...msg.content.action, 288 data: { ...msg.content.action.data, expire: 41 }, 289 }, 290 }, 291 }; 292 instance.executeAction(msgWithExpire); 293 294 assert.notCalled(instance.getExpirationDate); 295 assert.calledOnce(setStringPrefStub); 296 assert.calledWithExactly( 297 setStringPrefStub, 298 HOMEPAGE_OVERRIDE_PREF, 299 JSON.stringify({ 300 message_id: msg.id, 301 url: msg.content.action.data.url, 302 expire: 41, 303 }) 304 ); 305 }); 306 it("should send user telemetry", async () => { 307 const [msg] = await handleMessageRequestStub(); 308 const sendUserEventTelemetrySpy = sandbox.spy( 309 instance, 310 "sendUserEventTelemetry" 311 ); 312 instance.executeAction(msg); 313 314 assert.calledOnce(sendTelemetryStub); 315 assert.calledWithExactly(sendUserEventTelemetrySpy, msg); 316 assert.calledWithExactly(sendTelemetryStub, { 317 type: "MOMENTS_PAGE_TELEMETRY", 318 data: { 319 action: "moments_user_event", 320 bucket_id: "WNP_THANK_YOU", 321 event: "MOMENTS_PAGE_SET", 322 message_id: "WNP_THANK_YOU", 323 }, 324 }); 325 }); 326 }); 327 describe("#checkHomepageOverridePref", () => { 328 let messageRequestStub; 329 beforeEach(() => { 330 messageRequestStub = sandbox.stub(instance, "messageRequest"); 331 }); 332 it("should catch parse errors", () => { 333 getStringPrefStub.returns({}); 334 335 instance.checkHomepageOverridePref(); 336 337 assert.calledOnce(messageRequestStub); 338 assert.calledWithExactly(messageRequestStub, { 339 template: "update_action", 340 triggerId: "momentsUpdate", 341 }); 342 }); 343 }); 344 });