RecommendationProvider.test.js (9439B)
1 import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs"; 2 import { RecommendationProvider } from "lib/RecommendationProvider.sys.mjs"; 3 import { combineReducers, createStore } from "redux"; 4 import { reducers } from "common/Reducers.sys.mjs"; 5 import { GlobalOverrider } from "test/unit/utils"; 6 7 import { PersonalityProvider } from "lib/PersonalityProvider/PersonalityProvider.sys.mjs"; 8 import { PersistentCache } from "lib/PersistentCache.sys.mjs"; 9 10 const PREF_PERSONALIZATION_ENABLED = "discoverystream.personalization.enabled"; 11 const PREF_PERSONALIZATION_MODEL_KEYS = 12 "discoverystream.personalization.modelKeys"; 13 describe("RecommendationProvider", () => { 14 let feed; 15 let sandbox; 16 let clock; 17 let globals; 18 19 beforeEach(() => { 20 globals = new GlobalOverrider(); 21 globals.set({ 22 PersistentCache, 23 PersonalityProvider, 24 }); 25 26 sandbox = sinon.createSandbox(); 27 clock = sinon.useFakeTimers(); 28 feed = new RecommendationProvider(); 29 feed.store = createStore(combineReducers(reducers), {}); 30 }); 31 32 afterEach(() => { 33 sandbox.restore(); 34 clock.restore(); 35 globals.restore(); 36 }); 37 38 describe("#setProvider", () => { 39 it("should setup proper provider with modelKeys", async () => { 40 feed.setProvider(); 41 42 assert.equal(feed.provider.modelKeys, undefined); 43 44 feed.provider = null; 45 feed._modelKeys = "1234"; 46 47 feed.setProvider(); 48 49 assert.equal(feed.provider.modelKeys, "1234"); 50 feed._modelKeys = "12345"; 51 52 // Calling it again should not rebuild the provider. 53 feed.setProvider(); 54 assert.equal(feed.provider.modelKeys, "1234"); 55 }); 56 }); 57 58 describe("#calculateItemRelevanceScore", () => { 59 it("should use personalized score with provider", async () => { 60 const item = {}; 61 feed.provider = { 62 calculateItemRelevanceScore: async () => 0.5, 63 }; 64 await feed.calculateItemRelevanceScore(item); 65 assert.equal(item.score, 0.5); 66 }); 67 }); 68 69 describe("#teardown", () => { 70 it("should call provider.teardown ", () => { 71 sandbox.stub(global.Services.obs, "removeObserver").returns(); 72 feed.loaded = true; 73 feed.provider = { 74 teardown: sandbox.stub().resolves(), 75 }; 76 feed.teardown(); 77 assert.calledOnce(feed.provider.teardown); 78 assert.calledOnce(global.Services.obs.removeObserver); 79 assert.calledWith(global.Services.obs.removeObserver, feed, "idle-daily"); 80 }); 81 }); 82 83 describe("#resetState", () => { 84 it("should null affinityProviderV2 and affinityProvider", () => { 85 feed._modelKeys = {}; 86 feed.provider = {}; 87 88 feed.resetState(); 89 90 assert.equal(feed._modelKeys, null); 91 assert.equal(feed.provider, null); 92 }); 93 }); 94 95 describe("#onAction: DISCOVERY_STREAM_CONFIG_CHANGE", () => { 96 it("should call teardown, resetState, and setVersion", async () => { 97 sandbox.spy(feed, "teardown"); 98 sandbox.spy(feed, "resetState"); 99 feed.onAction({ 100 type: at.DISCOVERY_STREAM_CONFIG_CHANGE, 101 }); 102 assert.calledOnce(feed.teardown); 103 assert.calledOnce(feed.resetState); 104 }); 105 }); 106 107 describe("#onAction: PREF_CHANGED", () => { 108 beforeEach(() => { 109 sandbox.spy(feed.store, "dispatch"); 110 }); 111 it("should dispatch to DISCOVERY_STREAM_CONFIG_RESET PREF_PERSONALIZATION_MODEL_KEYS", async () => { 112 feed.onAction({ 113 type: at.PREF_CHANGED, 114 data: { 115 name: PREF_PERSONALIZATION_MODEL_KEYS, 116 }, 117 }); 118 119 assert.calledWith( 120 feed.store.dispatch, 121 ac.BroadcastToContent({ 122 type: at.DISCOVERY_STREAM_CONFIG_RESET, 123 }) 124 ); 125 }); 126 }); 127 128 describe("#personalizationOverride", () => { 129 it("should dispatch setPref", async () => { 130 sandbox.spy(feed.store, "dispatch"); 131 feed.store.getState = () => ({ 132 Prefs: { 133 values: { 134 "discoverystream.personalization.enabled": true, 135 }, 136 }, 137 }); 138 139 feed.personalizationOverride(true); 140 141 assert.calledWithMatch(feed.store.dispatch, { 142 data: { 143 name: "discoverystream.personalization.override", 144 value: true, 145 }, 146 type: at.SET_PREF, 147 }); 148 }); 149 it("should dispatch CLEAR_PREF", async () => { 150 sandbox.spy(feed.store, "dispatch"); 151 feed.store.getState = () => ({ 152 Prefs: { 153 values: { 154 "discoverystream.personalization.enabled": true, 155 "discoverystream.personalization.override": true, 156 }, 157 }, 158 }); 159 160 feed.personalizationOverride(false); 161 162 assert.calledWithMatch(feed.store.dispatch, { 163 data: { 164 name: "discoverystream.personalization.override", 165 }, 166 type: at.CLEAR_PREF, 167 }); 168 }); 169 }); 170 171 describe("#onAction: DISCOVERY_STREAM_DEV_IDLE_DAILY", () => { 172 it("should trigger idle-daily observer", async () => { 173 sandbox.stub(global.Services.obs, "notifyObservers").returns(); 174 await feed.onAction({ 175 type: at.DISCOVERY_STREAM_DEV_IDLE_DAILY, 176 }); 177 assert.calledWith( 178 global.Services.obs.notifyObservers, 179 null, 180 "idle-daily" 181 ); 182 }); 183 }); 184 185 describe("#onAction: INIT", () => { 186 it("should ", async () => { 187 sandbox.stub(feed, "enable").returns(); 188 await feed.onAction({ 189 type: at.INIT, 190 }); 191 assert.calledOnce(feed.enable); 192 assert.calledWith(feed.enable, true); 193 }); 194 }); 195 196 describe("#onAction: DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE", () => { 197 it("should ", async () => { 198 sandbox.stub(feed, "personalizationOverride").returns(); 199 await feed.onAction({ 200 type: at.DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE, 201 data: { override: true }, 202 }); 203 assert.calledOnce(feed.personalizationOverride); 204 assert.calledWith(feed.personalizationOverride, true); 205 }); 206 }); 207 208 describe("#loadPersonalizationScoresCache", () => { 209 it("should create a personalization provider from cached scores", async () => { 210 sandbox.spy(feed.store, "dispatch"); 211 sandbox.spy(feed.cache, "set"); 212 feed.provider = { 213 init: async () => {}, 214 getScores: () => "scores", 215 }; 216 feed.store.getState = () => ({ 217 Prefs: { 218 values: { 219 pocketConfig: { 220 recsPersonalized: true, 221 spocsPersonalized: true, 222 }, 223 "discoverystream.personalization.enabled": true, 224 "feeds.section.topstories": true, 225 "feeds.system.topstories": true, 226 }, 227 }, 228 }); 229 const fakeCache = { 230 personalization: { 231 scores: 123, 232 _timestamp: 456, 233 }, 234 }; 235 sandbox.stub(feed.cache, "get").returns(Promise.resolve(fakeCache)); 236 237 await feed.loadPersonalizationScoresCache(); 238 239 assert.equal(feed.personalizationLastUpdated, 456); 240 }); 241 }); 242 243 describe("#updatePersonalizationScores", () => { 244 beforeEach(() => { 245 sandbox.spy(feed.store, "dispatch"); 246 sandbox.spy(feed.cache, "set"); 247 sandbox.spy(feed, "setProvider"); 248 feed.provider = { 249 init: async () => {}, 250 getScores: () => "scores", 251 }; 252 }); 253 it("should update provider on updatePersonalizationScores", async () => { 254 feed.store.getState = () => ({ 255 Prefs: { 256 values: { 257 pocketConfig: { 258 recsPersonalized: true, 259 spocsPersonalized: true, 260 }, 261 "discoverystream.personalization.enabled": true, 262 "feeds.section.topstories": true, 263 "feeds.system.topstories": true, 264 }, 265 }, 266 }); 267 268 await feed.updatePersonalizationScores(); 269 270 assert.calledWith( 271 feed.store.dispatch, 272 ac.BroadcastToContent({ 273 type: at.DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED, 274 data: { 275 lastUpdated: 0, 276 }, 277 }) 278 ); 279 assert.calledWith(feed.cache.set, "personalization", { 280 scores: "scores", 281 _timestamp: 0, 282 }); 283 }); 284 it("should not update provider on updatePersonalizationScores", async () => { 285 feed.store.getState = () => ({ 286 Prefs: { 287 values: { 288 "discoverystream.spocs.personalized": true, 289 "discoverystream.recs.personalized": true, 290 "discoverystream.personalization.enabled": false, 291 }, 292 }, 293 }); 294 await feed.updatePersonalizationScores(); 295 296 assert.notCalled(feed.setProvider); 297 }); 298 }); 299 300 describe("#onAction: DISCOVERY_STREAM_PERSONALIZATION_TOGGLE", () => { 301 it("should fire SET_PREF with enabled", async () => { 302 sandbox.spy(feed.store, "dispatch"); 303 feed.store.getState = () => ({ 304 Prefs: { 305 values: { 306 [PREF_PERSONALIZATION_ENABLED]: false, 307 }, 308 }, 309 }); 310 311 await feed.onAction({ 312 type: at.DISCOVERY_STREAM_PERSONALIZATION_TOGGLE, 313 }); 314 assert.calledWith( 315 feed.store.dispatch, 316 ac.SetPref(PREF_PERSONALIZATION_ENABLED, true) 317 ); 318 }); 319 }); 320 321 describe("#observe", () => { 322 it("should call updatePersonalizationScores on idle daily", async () => { 323 sandbox.stub(feed, "updatePersonalizationScores").returns(); 324 feed.observe(null, "idle-daily"); 325 assert.calledOnce(feed.updatePersonalizationScores); 326 }); 327 }); 328 });