test_HighlightsFeed.js (38081B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 ChromeUtils.defineESModuleGetters(this, { 7 actionTypes: "resource://newtab/common/Actions.mjs", 8 BOOKMARKS_RESTORE_SUCCESS_EVENT: 9 "resource://newtab/lib/HighlightsFeed.sys.mjs", 10 BOOKMARKS_RESTORE_FAILED_EVENT: 11 "resource://newtab/lib/HighlightsFeed.sys.mjs", 12 FilterAdult: "resource:///modules/FilterAdult.sys.mjs", 13 HighlightsFeed: "resource://newtab/lib/HighlightsFeed.sys.mjs", 14 NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", 15 PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs", 16 sinon: "resource://testing-common/Sinon.sys.mjs", 17 Screenshots: "resource://newtab/lib/Screenshots.sys.mjs", 18 SectionsManager: "resource://newtab/lib/SectionsManager.sys.mjs", 19 SECTION_ID: "resource://newtab/lib/HighlightsFeed.sys.mjs", 20 SYNC_BOOKMARKS_FINISHED_EVENT: "resource://newtab/lib/HighlightsFeed.sys.mjs", 21 }); 22 23 const FAKE_LINKS = new Array(20) 24 .fill(null) 25 .map((v, i) => ({ url: `http://www.site${i}.com` })); 26 const FAKE_IMAGE = "data123"; 27 const FAKE_URL = "https://mozilla.org"; 28 const FAKE_IMAGE_URL = "https://mozilla.org/preview.jpg"; 29 30 function getHighlightsFeedForTest(sandbox) { 31 let feed = new HighlightsFeed(); 32 feed.store = { 33 dispatch: sandbox.spy(), 34 getState() { 35 return this.state; 36 }, 37 state: { 38 Prefs: { 39 values: { 40 "section.highlights.includeDownloads": false, 41 }, 42 }, 43 TopSites: { 44 initialized: true, 45 rows: Array(12) 46 .fill(null) 47 .map((v, i) => ({ url: `http://www.topsite${i}.com` })), 48 }, 49 Sections: [{ id: "highlights", initialized: false }], 50 }, 51 subscribe: sandbox.stub().callsFake(cb => { 52 cb(); 53 return () => {}; 54 }), 55 }; 56 57 sandbox 58 .stub(NewTabUtils.activityStreamLinks, "getHighlights") 59 .resolves(FAKE_LINKS); 60 sandbox 61 .stub(NewTabUtils.activityStreamProvider, "_processHighlights") 62 .callsFake(l => l.slice(0, 1)); 63 64 return feed; 65 } 66 67 async function fetchHighlightsRows(feed, options) { 68 let sandbox = sinon.createSandbox(); 69 sandbox.stub(SectionsManager, "updateSection"); 70 await feed.fetchHighlights(options); 71 let [, { rows }] = SectionsManager.updateSection.firstCall.args; 72 73 sandbox.restore(); 74 return rows; 75 } 76 77 function fetchImage(feed, page) { 78 return feed.fetchImage( 79 Object.assign({ __sharedCache: { updateLink() {} } }, page) 80 ); 81 } 82 83 add_task(function test_construction() { 84 info("HighlightsFeed construction should work"); 85 let sandbox = sinon.createSandbox(); 86 sandbox.stub(PageThumbs, "addExpirationFilter"); 87 88 let feed = getHighlightsFeedForTest(sandbox); 89 Assert.ok(feed, "Was able to create a HighlightsFeed"); 90 91 info("HighlightsFeed construction should add a PageThumbs expiration filter"); 92 Assert.ok( 93 PageThumbs.addExpirationFilter.calledOnce, 94 "PageThumbs.addExpirationFilter was called once" 95 ); 96 97 sandbox.restore(); 98 }); 99 100 add_task(function test_init_action() { 101 let sandbox = sinon.createSandbox(); 102 103 let countObservers = topic => { 104 return [...Services.obs.enumerateObservers(topic)].length; 105 }; 106 107 const INITIAL_SYNC_BOOKMARKS_FINISHED_EVENT_COUNT = countObservers( 108 SYNC_BOOKMARKS_FINISHED_EVENT 109 ); 110 const INITIAL_BOOKMARKS_RESTORE_SUCCESS_EVENT_COUNT = countObservers( 111 BOOKMARKS_RESTORE_SUCCESS_EVENT 112 ); 113 const INITIAL_BOOKMARKS_RESTORE_FAILED_EVENT_COUNT = countObservers( 114 BOOKMARKS_RESTORE_FAILED_EVENT 115 ); 116 117 sandbox 118 .stub(SectionsManager, "onceInitialized") 119 .callsFake(callback => callback()); 120 sandbox.stub(SectionsManager, "enableSection"); 121 122 let feed = getHighlightsFeedForTest(sandbox); 123 sandbox.stub(feed, "fetchHighlights"); 124 sandbox.stub(feed.downloadsManager, "init"); 125 126 feed.onAction({ type: actionTypes.INIT }); 127 128 info("HighlightsFeed.onAction(INIT) should add a sync observer"); 129 Assert.equal( 130 countObservers(SYNC_BOOKMARKS_FINISHED_EVENT), 131 INITIAL_SYNC_BOOKMARKS_FINISHED_EVENT_COUNT + 1 132 ); 133 Assert.equal( 134 countObservers(BOOKMARKS_RESTORE_SUCCESS_EVENT), 135 INITIAL_BOOKMARKS_RESTORE_SUCCESS_EVENT_COUNT + 1 136 ); 137 Assert.equal( 138 countObservers(BOOKMARKS_RESTORE_FAILED_EVENT), 139 INITIAL_BOOKMARKS_RESTORE_FAILED_EVENT_COUNT + 1 140 ); 141 142 info( 143 "HighlightsFeed.onAction(INIT) should call SectionsManager.onceInitialized" 144 ); 145 Assert.ok( 146 SectionsManager.onceInitialized.calledOnce, 147 "SectionsManager.onceInitialized was called" 148 ); 149 150 info("HighlightsFeed.onAction(INIT) should enable its section"); 151 Assert.ok( 152 SectionsManager.enableSection.calledOnce, 153 "SectionsManager.enableSection was called" 154 ); 155 Assert.ok(SectionsManager.enableSection.calledWith(SECTION_ID)); 156 157 info("HighlightsFeed.onAction(INIT) should fetch highlights"); 158 Assert.ok( 159 feed.fetchHighlights.calledOnce, 160 "HighlightsFeed.fetchHighlights was called" 161 ); 162 163 info("HighlightsFeed.onAction(INIT) should initialize the DownloadsManager"); 164 Assert.ok( 165 feed.downloadsManager.init.calledOnce, 166 "HighlightsFeed.downloadsManager.init was called" 167 ); 168 169 feed.uninit(); 170 // Let's make sure that uninit also removed these observers while we're here. 171 Assert.equal( 172 countObservers(SYNC_BOOKMARKS_FINISHED_EVENT), 173 INITIAL_SYNC_BOOKMARKS_FINISHED_EVENT_COUNT 174 ); 175 Assert.equal( 176 countObservers(BOOKMARKS_RESTORE_SUCCESS_EVENT), 177 INITIAL_BOOKMARKS_RESTORE_SUCCESS_EVENT_COUNT 178 ); 179 Assert.equal( 180 countObservers(BOOKMARKS_RESTORE_FAILED_EVENT), 181 INITIAL_BOOKMARKS_RESTORE_FAILED_EVENT_COUNT 182 ); 183 184 sandbox.restore(); 185 }); 186 187 add_task(async function test_observe_fetch_highlights() { 188 let topicDataPairs = [ 189 { 190 description: 191 "should fetch highlights when we are done a sync for bookmarks", 192 shouldFetch: true, 193 topic: SYNC_BOOKMARKS_FINISHED_EVENT, 194 data: "bookmarks", 195 }, 196 { 197 description: "should fetch highlights after a successful import", 198 shouldFetch: true, 199 topic: BOOKMARKS_RESTORE_SUCCESS_EVENT, 200 data: "html", 201 }, 202 { 203 description: "should fetch highlights after a failed import", 204 shouldFetch: true, 205 topic: BOOKMARKS_RESTORE_FAILED_EVENT, 206 data: "json", 207 }, 208 { 209 description: 210 "should not fetch highlights when we are doing a sync for something that is not bookmarks", 211 shouldFetch: false, 212 topic: SYNC_BOOKMARKS_FINISHED_EVENT, 213 data: "tabs", 214 }, 215 { 216 description: "should not fetch highlights after a successful import", 217 shouldFetch: false, 218 topic: "someotherevent", 219 data: "bookmarks", 220 }, 221 ]; 222 223 for (let topicDataPair of topicDataPairs) { 224 info(`HighlightsFeed.observe ${topicDataPair.description}`); 225 let sandbox = sinon.createSandbox(); 226 let feed = getHighlightsFeedForTest(sandbox); 227 sandbox.stub(feed, "fetchHighlights"); 228 feed.observe(null, topicDataPair.topic, topicDataPair.data); 229 230 if (topicDataPair.shouldFetch) { 231 Assert.ok( 232 feed.fetchHighlights.calledOnce, 233 "HighlightsFeed.fetchHighlights was called" 234 ); 235 Assert.ok(feed.fetchHighlights.calledWith({ broadcast: true })); 236 } else { 237 Assert.ok( 238 feed.fetchHighlights.notCalled, 239 "HighlightsFeed.fetchHighlights was not called" 240 ); 241 } 242 243 sandbox.restore(); 244 } 245 }); 246 247 add_task(async function test_filterForThumbnailExpiration_calls() { 248 info( 249 "HighlightsFeed.filterForThumbnailExpiration should pass rows.urls " + 250 "to the callback provided" 251 ); 252 let sandbox = sinon.createSandbox(); 253 let feed = getHighlightsFeedForTest(sandbox); 254 let rows = [{ url: "foo.com" }, { url: "bar.com" }]; 255 256 feed.store.state.Sections = [{ id: "highlights", rows, initialized: true }]; 257 const stub = sinon.stub(); 258 259 feed.filterForThumbnailExpiration(stub); 260 261 Assert.ok(stub.calledOnce, "Filter was called"); 262 Assert.ok(stub.calledWithExactly(rows.map(r => r.url))); 263 264 sandbox.restore(); 265 }); 266 267 add_task( 268 async function test_filterForThumbnailExpiration_include_preview_image_url() { 269 info( 270 "HighlightsFeed.filterForThumbnailExpiration should include " + 271 "preview_image_url (if present) in the callback results" 272 ); 273 let sandbox = sinon.createSandbox(); 274 let feed = getHighlightsFeedForTest(sandbox); 275 let rows = [ 276 { url: "foo.com" }, 277 { url: "bar.com", preview_image_url: "bar.jpg" }, 278 ]; 279 280 feed.store.state.Sections = [{ id: "highlights", rows, initialized: true }]; 281 const stub = sinon.stub(); 282 283 feed.filterForThumbnailExpiration(stub); 284 285 Assert.ok(stub.calledOnce, "Filter was called"); 286 Assert.ok(stub.calledWithExactly(["foo.com", "bar.com", "bar.jpg"])); 287 288 sandbox.restore(); 289 } 290 ); 291 292 add_task(async function test_filterForThumbnailExpiration_not_initialized() { 293 info( 294 "HighlightsFeed.filterForThumbnailExpiration should pass an empty " + 295 "array if not initialized" 296 ); 297 let sandbox = sinon.createSandbox(); 298 let feed = getHighlightsFeedForTest(sandbox); 299 let rows = [{ url: "foo.com" }, { url: "bar.com" }]; 300 301 feed.store.state.Sections = [{ rows, initialized: false }]; 302 const stub = sinon.stub(); 303 304 feed.filterForThumbnailExpiration(stub); 305 306 Assert.ok(stub.calledOnce, "Filter was called"); 307 Assert.ok(stub.calledWithExactly([])); 308 309 sandbox.restore(); 310 }); 311 312 add_task(async function test_fetchHighlights_TopSites_not_initialized() { 313 info( 314 "HighlightsFeed.fetchHighlights should return early if TopSites are not " + 315 "initialized" 316 ); 317 let sandbox = sinon.createSandbox(); 318 let feed = getHighlightsFeedForTest(sandbox); 319 320 sandbox.spy(feed.linksCache, "request"); 321 322 feed.store.state.TopSites.initialized = false; 323 feed.store.state.Prefs.values["feeds.topsites"] = true; 324 feed.store.state.Prefs.values["feeds.system.topsites"] = true; 325 326 // Initially TopSites is uninitialised and fetchHighlights should return. 327 await feed.fetchHighlights(); 328 329 Assert.ok( 330 NewTabUtils.activityStreamLinks.getHighlights.notCalled, 331 "NewTabUtils.activityStreamLinks.getHighlights was not called" 332 ); 333 Assert.ok( 334 feed.linksCache.request.notCalled, 335 "HighlightsFeed.linksCache.request was not called" 336 ); 337 338 sandbox.restore(); 339 }); 340 341 add_task(async function test_fetchHighlights_sections_not_initialized() { 342 info( 343 "HighlightsFeed.fetchHighlights should return early if Sections are not " + 344 "initialized" 345 ); 346 let sandbox = sinon.createSandbox(); 347 let feed = getHighlightsFeedForTest(sandbox); 348 349 sandbox.spy(feed.linksCache, "request"); 350 351 feed.store.state.TopSites.initialized = true; 352 feed.store.state.Prefs.values["feeds.topsites"] = true; 353 feed.store.state.Prefs.values["feeds.system.topsites"] = true; 354 feed.store.state.Sections = []; 355 356 await feed.fetchHighlights(); 357 358 Assert.ok( 359 NewTabUtils.activityStreamLinks.getHighlights.notCalled, 360 "NewTabUtils.activityStreamLinks.getHighlights was not called" 361 ); 362 Assert.ok( 363 feed.linksCache.request.notCalled, 364 "HighlightsFeed.linksCache.request was not called" 365 ); 366 367 sandbox.restore(); 368 }); 369 370 add_task(async function test_fetchHighlights_TopSites_initialized() { 371 info( 372 "HighlightsFeed.fetchHighlights should fetch Highlights if TopSites are " + 373 "initialised" 374 ); 375 let sandbox = sinon.createSandbox(); 376 let feed = getHighlightsFeedForTest(sandbox); 377 378 sandbox.spy(feed.linksCache, "request"); 379 380 // fetchHighlights should continue 381 feed.store.state.TopSites.initialized = true; 382 383 await feed.fetchHighlights(); 384 385 Assert.ok( 386 NewTabUtils.activityStreamLinks.getHighlights.calledOnce, 387 "NewTabUtils.activityStreamLinks.getHighlights was called" 388 ); 389 Assert.ok( 390 feed.linksCache.request.calledOnce, 391 "HighlightsFeed.linksCache.request was called" 392 ); 393 394 sandbox.restore(); 395 }); 396 397 add_task(async function test_fetchHighlights_chronological_order() { 398 info( 399 "HighlightsFeed.fetchHighlights should chronologically order highlight " + 400 "data types" 401 ); 402 let sandbox = sinon.createSandbox(); 403 let feed = getHighlightsFeedForTest(sandbox); 404 405 let links = [ 406 { 407 url: "https://site0.com", 408 type: "bookmark", 409 bookmarkGuid: "1234", 410 date_added: Date.now() - 80, 411 }, // 3rd newest 412 { 413 url: "https://site1.com", 414 type: "history", 415 bookmarkGuid: "1234", 416 date_added: Date.now() - 60, 417 }, // append at the end 418 { 419 url: "https://site2.com", 420 type: "history", 421 date_added: Date.now() - 160, 422 }, // append at the end 423 { 424 url: "https://site3.com", 425 type: "history", 426 date_added: Date.now() - 60, 427 }, // append at the end 428 { 429 url: "https://site4.com", 430 type: "bookmark", 431 bookmarkGuid: "1234", 432 date_added: Date.now(), 433 }, // newest highlight 434 { 435 url: "https://site5.com", 436 type: "bookmark", 437 bookmarkGuid: "12345", 438 date_added: Date.now() - 100, 439 }, // 4th newest 440 { 441 url: "https://site6.com", 442 type: "bookmark", 443 bookmarkGuid: "1234", 444 date_added: Date.now() - 40, 445 }, // 2nd newest 446 ]; 447 let expectedChronological = [4, 6, 0, 5]; 448 let expectedHistory = [1, 2, 3]; 449 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 450 451 let highlights = await fetchHighlightsRows(feed); 452 453 [...expectedChronological, ...expectedHistory].forEach((link, index) => { 454 Assert.equal( 455 highlights[index].url, 456 links[link].url, 457 `highlight[${index}] should be link[${link}]` 458 ); 459 }); 460 461 sandbox.restore(); 462 }); 463 464 add_task(async function test_fetchHighlights_TopSites_not_enabled() { 465 info( 466 "HighlightsFeed.fetchHighlights should fetch Highlights if TopSites " + 467 "are not enabled" 468 ); 469 let sandbox = sinon.createSandbox(); 470 let feed = getHighlightsFeedForTest(sandbox); 471 472 sandbox.spy(feed.linksCache, "request"); 473 474 feed.store.state.Prefs.values["feeds.system.topsites"] = false; 475 476 await feed.fetchHighlights(); 477 478 Assert.ok( 479 NewTabUtils.activityStreamLinks.getHighlights.calledOnce, 480 "NewTabUtils.activityStreamLinks.getHighlights was called" 481 ); 482 Assert.ok( 483 feed.linksCache.request.calledOnce, 484 "HighlightsFeed.linksCache.request was called" 485 ); 486 487 sandbox.restore(); 488 }); 489 490 add_task(async function test_fetchHighlights_TopSites_not_shown() { 491 info( 492 "HighlightsFeed.fetchHighlights should fetch Highlights if TopSites " + 493 "are not shown on NTP" 494 ); 495 let sandbox = sinon.createSandbox(); 496 let feed = getHighlightsFeedForTest(sandbox); 497 498 sandbox.spy(feed.linksCache, "request"); 499 500 feed.store.state.Prefs.values["feeds.topsites"] = false; 501 502 await feed.fetchHighlights(); 503 504 Assert.ok( 505 NewTabUtils.activityStreamLinks.getHighlights.calledOnce, 506 "NewTabUtils.activityStreamLinks.getHighlights was called" 507 ); 508 Assert.ok( 509 feed.linksCache.request.calledOnce, 510 "HighlightsFeed.linksCache.request was called" 511 ); 512 513 sandbox.restore(); 514 }); 515 516 add_task(async function test_fetchHighlights_add_hostname_hasImage() { 517 info( 518 "HighlightsFeed.fetchHighlights should add shortURL hostname and hasImage to each link" 519 ); 520 let sandbox = sinon.createSandbox(); 521 let feed = getHighlightsFeedForTest(sandbox); 522 523 let links = [{ url: "https://mozilla.org" }]; 524 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 525 526 let highlights = await fetchHighlightsRows(feed); 527 528 Assert.equal(highlights[0].hostname, NewTabUtils.shortURL(links[0])); 529 Assert.equal(highlights[0].hasImage, true); 530 531 sandbox.restore(); 532 }); 533 534 add_task(async function test_fetchHighlights_add_existing_image() { 535 info( 536 "HighlightsFeed.fetchHighlights should add an existing image if it " + 537 "exists to the link without calling fetchImage" 538 ); 539 let sandbox = sinon.createSandbox(); 540 let feed = getHighlightsFeedForTest(sandbox); 541 542 let links = [{ url: "https://mozilla.org", image: FAKE_IMAGE }]; 543 sandbox.spy(feed, "fetchImage"); 544 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 545 546 let highlights = await fetchHighlightsRows(feed); 547 548 Assert.equal(highlights[0].image, FAKE_IMAGE); 549 Assert.ok(feed.fetchImage.notCalled, "HighlightsFeed.fetchImage not called"); 550 551 sandbox.restore(); 552 }); 553 554 add_task(async function test_fetchHighlights_correct_args() { 555 info( 556 "HighlightsFeed.fetchHighlights should call fetchImage with the correct " + 557 "arguments for new links" 558 ); 559 let sandbox = sinon.createSandbox(); 560 let feed = getHighlightsFeedForTest(sandbox); 561 562 let links = [ 563 { 564 url: "https://mozilla.org", 565 preview_image_url: "https://mozilla.org/preview.jog", 566 }, 567 ]; 568 sandbox.spy(feed, "fetchImage"); 569 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 570 571 await fetchHighlightsRows(feed); 572 573 Assert.ok(feed.fetchImage.calledOnce, "HighlightsFeed.fetchImage called"); 574 575 let [arg] = feed.fetchImage.firstCall.args; 576 Assert.equal(arg.url, links[0].url); 577 Assert.equal(arg.preview_image_url, links[0].preview_image_url); 578 579 sandbox.restore(); 580 }); 581 582 add_task( 583 async function test_fetchHighlights_not_include_links_already_in_TopSites() { 584 info( 585 "HighlightsFeed.fetchHighlights should not include any links already in " + 586 "Top Sites" 587 ); 588 let sandbox = sinon.createSandbox(); 589 let feed = getHighlightsFeedForTest(sandbox); 590 591 let links = [ 592 { url: "https://mozilla.org" }, 593 { url: "http://www.topsite0.com" }, 594 { url: "http://www.topsite1.com" }, 595 { url: "http://www.topsite2.com" }, 596 ]; 597 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 598 599 let highlights = await fetchHighlightsRows(feed); 600 601 Assert.equal(highlights.length, 1); 602 Assert.equal(highlights[0].url, links[0].url); 603 604 sandbox.restore(); 605 } 606 ); 607 608 add_task( 609 async function test_fetchHighlights_not_include_history_already_in_TopSites() { 610 info( 611 "HighlightsFeed.fetchHighlights should include bookmark but not " + 612 "history already in Top Sites" 613 ); 614 let sandbox = sinon.createSandbox(); 615 let feed = getHighlightsFeedForTest(sandbox); 616 617 let links = [ 618 { url: "http://www.topsite0.com", type: "bookmark" }, 619 { url: "http://www.topsite1.com", type: "history" }, 620 ]; 621 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 622 623 let highlights = await fetchHighlightsRows(feed); 624 625 Assert.equal(highlights.length, 1); 626 Assert.equal(highlights[0].url, links[0].url); 627 628 sandbox.restore(); 629 } 630 ); 631 632 add_task( 633 async function test_fetchHighlights_not_include_history_same_hostname_as_bookmark() { 634 info( 635 "HighlightsFeed.fetchHighlights should not include history of same " + 636 "hostname as a bookmark" 637 ); 638 let sandbox = sinon.createSandbox(); 639 let feed = getHighlightsFeedForTest(sandbox); 640 641 let links = [ 642 { url: "https://site.com/bookmark", type: "bookmark" }, 643 { url: "https://site.com/history", type: "history" }, 644 ]; 645 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 646 647 let highlights = await fetchHighlightsRows(feed); 648 649 Assert.equal(highlights.length, 1); 650 Assert.equal(highlights[0].url, links[0].url); 651 652 sandbox.restore(); 653 } 654 ); 655 656 add_task(async function test_fetchHighlights_take_first_history_of_hostname() { 657 info( 658 "HighlightsFeed.fetchHighlights should take the first history of a hostname" 659 ); 660 let sandbox = sinon.createSandbox(); 661 let feed = getHighlightsFeedForTest(sandbox); 662 663 let links = [ 664 { url: "https://site.com/first", type: "history" }, 665 { url: "https://site.com/second", type: "history" }, 666 { url: "https://other", type: "history" }, 667 ]; 668 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 669 670 let highlights = await fetchHighlightsRows(feed); 671 672 Assert.equal(highlights.length, 2); 673 Assert.equal(highlights[0].url, links[0].url); 674 Assert.equal(highlights[1].url, links[2].url); 675 676 sandbox.restore(); 677 }); 678 679 add_task( 680 async function test_fetchHighlights_take_bookmark_pocket_download_of_same_hostname() { 681 info( 682 "HighlightsFeed.fetchHighlights should take a bookmark, a pocket, and " + 683 "downloaded item of the same hostname" 684 ); 685 let sandbox = sinon.createSandbox(); 686 let feed = getHighlightsFeedForTest(sandbox); 687 688 let links = [ 689 { url: "https://site.com/bookmark", type: "bookmark" }, 690 { url: "https://site.com/pocket", type: "pocket" }, 691 { url: "https://site.com/download", type: "download" }, 692 ]; 693 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 694 695 let highlights = await fetchHighlightsRows(feed); 696 697 Assert.equal(highlights.length, 3); 698 Assert.equal(highlights[0].url, links[0].url); 699 Assert.equal(highlights[1].url, links[1].url); 700 Assert.equal(highlights[2].url, links[2].url); 701 702 sandbox.restore(); 703 } 704 ); 705 706 add_task(async function test_fetchHighlights_do_not_include_downloads() { 707 info( 708 "HighlightsFeed.fetchHighlights should not include downloads when " + 709 "includeDownloads pref is false" 710 ); 711 let sandbox = sinon.createSandbox(); 712 let feed = getHighlightsFeedForTest(sandbox); 713 feed.store.state.Prefs.values["section.highlights.includeDownloads"] = false; 714 feed.downloadsManager.getDownloads = () => [ 715 { url: "https://site1.com/download" }, 716 { url: "https://site2.com/download" }, 717 ]; 718 719 let links = [ 720 { url: "https://site.com/bookmark", type: "bookmark" }, 721 { url: "https://site.com/pocket", type: "pocket" }, 722 ]; 723 724 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 725 726 let highlights = await fetchHighlightsRows(feed); 727 728 Assert.equal(highlights.length, 2); 729 Assert.equal(highlights[0].url, links[0].url); 730 Assert.equal(highlights[1].url, links[1].url); 731 732 sandbox.restore(); 733 }); 734 735 add_task(async function test_fetchHighlights_include_downloads() { 736 info( 737 "HighlightsFeed.fetchHighlights should include downloads when " + 738 "includeDownloads pref is true" 739 ); 740 let sandbox = sinon.createSandbox(); 741 let feed = getHighlightsFeedForTest(sandbox); 742 feed.store.state.Prefs.values["section.highlights.includeDownloads"] = true; 743 feed.downloadsManager.getDownloads = () => [ 744 { url: "https://site.com/download" }, 745 ]; 746 747 let links = [ 748 { url: "https://site.com/bookmark", type: "bookmark" }, 749 { url: "https://site.com/pocket", type: "pocket" }, 750 ]; 751 752 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 753 754 let highlights = await fetchHighlightsRows(feed); 755 756 Assert.equal(highlights.length, 3); 757 Assert.equal(highlights[0].url, links[0].url); 758 Assert.equal(highlights[1].url, links[1].url); 759 Assert.equal(highlights[2].url, "https://site.com/download"); 760 Assert.equal(highlights[2].type, "download"); 761 762 sandbox.restore(); 763 }); 764 765 add_task(async function test_fetchHighlights_take_one_download() { 766 info("HighlightsFeed.fetchHighlights should only take 1 download"); 767 let sandbox = sinon.createSandbox(); 768 let feed = getHighlightsFeedForTest(sandbox); 769 feed.store.state.Prefs.values["section.highlights.includeDownloads"] = true; 770 feed.downloadsManager.getDownloads = () => [ 771 { url: "https://site1.com/download" }, 772 { url: "https://site2.com/download" }, 773 ]; 774 775 let links = [{ url: "https://site.com/bookmark", type: "bookmark" }]; 776 777 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 778 779 let highlights = await fetchHighlightsRows(feed); 780 781 Assert.equal(highlights.length, 2); 782 Assert.equal(highlights[0].url, links[0].url); 783 Assert.equal(highlights[1].url, "https://site1.com/download"); 784 785 sandbox.restore(); 786 }); 787 788 add_task(async function test_fetchHighlights_chronological_sort() { 789 info( 790 "HighlightsFeed.fetchHighlights should sort bookmarks and downloads chronologically" 791 ); 792 let sandbox = sinon.createSandbox(); 793 let feed = getHighlightsFeedForTest(sandbox); 794 feed.store.state.Prefs.values["section.highlights.includeDownloads"] = true; 795 feed.downloadsManager.getDownloads = () => [ 796 { 797 url: "https://site1.com/download", 798 type: "download", 799 date_added: Date.now(), 800 }, 801 ]; 802 803 let links = [ 804 { 805 url: "https://site.com/bookmark", 806 type: "bookmark", 807 date_added: Date.now() - 10000, 808 }, 809 { 810 url: "https://site2.com/another-bookmark", 811 type: "bookmark", 812 date_added: Date.now() - 5000, 813 }, 814 { 815 url: "https://site3.com/visited", 816 type: "history", 817 date_added: Date.now(), 818 }, 819 ]; 820 821 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 822 823 let highlights = await fetchHighlightsRows(feed); 824 825 Assert.equal(highlights.length, 4); 826 Assert.equal(highlights[0].url, "https://site1.com/download"); 827 Assert.equal(highlights[1].url, links[1].url); 828 Assert.equal(highlights[2].url, links[0].url); 829 Assert.equal(highlights[3].url, links[2].url); // history item goes last 830 831 sandbox.restore(); 832 }); 833 834 add_task( 835 async function test_fetchHighlights_set_type_to_bookmark_on_bookmarkGuid() { 836 info( 837 "HighlightsFeed.fetchHighlights should set type to bookmark if there " + 838 "is a bookmarkGuid" 839 ); 840 let sandbox = sinon.createSandbox(); 841 let feed = getHighlightsFeedForTest(sandbox); 842 feed.store.state.Prefs.values["section.highlights.includeBookmarks"] = true; 843 feed.downloadsManager.getDownloads = () => [ 844 { 845 url: "https://site1.com/download", 846 type: "download", 847 date_added: Date.now(), 848 }, 849 ]; 850 851 let links = [ 852 { 853 url: "https://mozilla.org", 854 type: "history", 855 bookmarkGuid: "1234567890", 856 }, 857 ]; 858 859 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 860 861 let highlights = await fetchHighlightsRows(feed); 862 863 Assert.equal(highlights[0].type, "bookmark"); 864 865 sandbox.restore(); 866 } 867 ); 868 869 add_task( 870 async function test_fetchHighlights_keep_history_type_on_bookmarkGuid() { 871 info( 872 "HighlightsFeed.fetchHighlights should keep history type if there is a " + 873 "bookmarkGuid but don't include bookmarks" 874 ); 875 let sandbox = sinon.createSandbox(); 876 let feed = getHighlightsFeedForTest(sandbox); 877 feed.store.state.Prefs.values["section.highlights.includeBookmarks"] = 878 false; 879 880 let links = [ 881 { 882 url: "https://mozilla.org", 883 type: "history", 884 bookmarkGuid: "1234567890", 885 }, 886 ]; 887 888 NewTabUtils.activityStreamLinks.getHighlights.resolves(links); 889 890 let highlights = await fetchHighlightsRows(feed); 891 892 Assert.equal(highlights[0].type, "history"); 893 894 sandbox.restore(); 895 } 896 ); 897 898 add_task(async function test_fetchHighlights_filter_adult() { 899 info("HighlightsFeed.fetchHighlights should filter out adult pages"); 900 let sandbox = sinon.createSandbox(); 901 let feed = getHighlightsFeedForTest(sandbox); 902 903 sandbox.stub(FilterAdult, "filter").returns([]); 904 let highlights = await fetchHighlightsRows(feed); 905 906 Assert.ok(FilterAdult.filter.calledOnce, "FilterAdult.filter called"); 907 Assert.equal(highlights.length, 0); 908 909 sandbox.restore(); 910 }); 911 912 add_task(async function test_fetchHighlights_no_expose_internal_link_props() { 913 info( 914 "HighlightsFeed.fetchHighlights should not expose internal link properties" 915 ); 916 let sandbox = sinon.createSandbox(); 917 let feed = getHighlightsFeedForTest(sandbox); 918 919 let highlights = await fetchHighlightsRows(feed); 920 let internal = Object.keys(highlights[0]).filter(key => key.startsWith("__")); 921 922 Assert.equal(internal.join(""), ""); 923 924 sandbox.restore(); 925 }); 926 927 add_task( 928 async function test_fetchHighlights_broadcast_when_feed_not_initialized() { 929 info( 930 "HighlightsFeed.fetchHighlights should broadcast if feed is not initialized" 931 ); 932 let sandbox = sinon.createSandbox(); 933 let feed = getHighlightsFeedForTest(sandbox); 934 935 NewTabUtils.activityStreamLinks.getHighlights.resolves([]); 936 sandbox.stub(SectionsManager, "updateSection"); 937 await feed.fetchHighlights(); 938 939 Assert.ok( 940 SectionsManager.updateSection.calledOnce, 941 "SectionsManager.updateSection called once" 942 ); 943 Assert.ok( 944 SectionsManager.updateSection.calledWithExactly( 945 SECTION_ID, 946 { rows: [] }, 947 true, 948 undefined 949 ) 950 ); 951 sandbox.restore(); 952 } 953 ); 954 955 add_task( 956 async function test_fetchHighlights_broadcast_on_broadcast_in_options() { 957 info( 958 "HighlightsFeed.fetchHighlights should broadcast if options.broadcast is true" 959 ); 960 let sandbox = sinon.createSandbox(); 961 let feed = getHighlightsFeedForTest(sandbox); 962 963 NewTabUtils.activityStreamLinks.getHighlights.resolves([]); 964 feed.store.state.Sections[0].initialized = true; 965 966 sandbox.stub(SectionsManager, "updateSection"); 967 await feed.fetchHighlights({ broadcast: true }); 968 969 Assert.ok( 970 SectionsManager.updateSection.calledOnce, 971 "SectionsManager.updateSection called once" 972 ); 973 Assert.ok( 974 SectionsManager.updateSection.calledWithExactly( 975 SECTION_ID, 976 { rows: [] }, 977 true, 978 undefined 979 ) 980 ); 981 sandbox.restore(); 982 } 983 ); 984 985 add_task(async function test_fetchHighlights_no_broadcast() { 986 info( 987 "HighlightsFeed.fetchHighlights should not broadcast if " + 988 "options.broadcast is false and initialized is true" 989 ); 990 let sandbox = sinon.createSandbox(); 991 let feed = getHighlightsFeedForTest(sandbox); 992 993 NewTabUtils.activityStreamLinks.getHighlights.resolves([]); 994 feed.store.state.Sections[0].initialized = true; 995 996 sandbox.stub(SectionsManager, "updateSection"); 997 await feed.fetchHighlights({ broadcast: false }); 998 999 Assert.ok( 1000 SectionsManager.updateSection.calledOnce, 1001 "SectionsManager.updateSection called once" 1002 ); 1003 Assert.ok( 1004 SectionsManager.updateSection.calledWithExactly( 1005 SECTION_ID, 1006 { rows: [] }, 1007 false, 1008 undefined 1009 ) 1010 ); 1011 sandbox.restore(); 1012 }); 1013 1014 add_task(async function test_fetchImage_capture_if_available() { 1015 info("HighlightsFeed.fetchImage should capture the image, if available"); 1016 let sandbox = sinon.createSandbox(); 1017 let feed = getHighlightsFeedForTest(sandbox); 1018 1019 sandbox.stub(Screenshots, "getScreenshotForURL"); 1020 sandbox.stub(Screenshots, "_shouldGetScreenshots").returns(true); 1021 1022 await fetchImage(feed, { 1023 preview_image_url: FAKE_IMAGE_URL, 1024 url: FAKE_URL, 1025 }); 1026 1027 Assert.ok( 1028 Screenshots.getScreenshotForURL.calledOnce, 1029 "Screenshots.getScreenshotForURL called once" 1030 ); 1031 Assert.ok(Screenshots.getScreenshotForURL.calledWith(FAKE_IMAGE_URL)); 1032 1033 sandbox.restore(); 1034 }); 1035 1036 add_task(async function test_fetchImage_fallback_to_screenshot() { 1037 info("HighlightsFeed.fetchImage should fall back to capturing a screenshot"); 1038 let sandbox = sinon.createSandbox(); 1039 let feed = getHighlightsFeedForTest(sandbox); 1040 1041 sandbox.stub(Screenshots, "getScreenshotForURL"); 1042 sandbox.stub(Screenshots, "_shouldGetScreenshots").returns(true); 1043 1044 await fetchImage(feed, { url: FAKE_URL }); 1045 1046 Assert.ok( 1047 Screenshots.getScreenshotForURL.calledOnce, 1048 "Screenshots.getScreenshotForURL called once" 1049 ); 1050 Assert.ok(Screenshots.getScreenshotForURL.calledWith(FAKE_URL)); 1051 1052 sandbox.restore(); 1053 }); 1054 1055 add_task(async function test_fetchImage_updateSectionCard_args() { 1056 info( 1057 "HighlightsFeed.fetchImage should call " + 1058 "SectionsManager.updateSectionCard with the right arguments" 1059 ); 1060 let sandbox = sinon.createSandbox(); 1061 let feed = getHighlightsFeedForTest(sandbox); 1062 1063 sandbox.stub(SectionsManager, "updateSectionCard"); 1064 sandbox.stub(Screenshots, "getScreenshotForURL").resolves(FAKE_IMAGE); 1065 sandbox.stub(Screenshots, "_shouldGetScreenshots").returns(true); 1066 1067 await fetchImage(feed, { 1068 preview_image_url: FAKE_IMAGE_URL, 1069 url: FAKE_URL, 1070 }); 1071 Assert.ok( 1072 SectionsManager.updateSectionCard.calledOnce, 1073 "SectionsManager.updateSectionCard called" 1074 ); 1075 Assert.ok( 1076 SectionsManager.updateSectionCard.calledWith( 1077 "highlights", 1078 FAKE_URL, 1079 { image: FAKE_IMAGE }, 1080 true 1081 ) 1082 ); 1083 sandbox.restore(); 1084 }); 1085 1086 add_task(async function test_fetchImage_no_update_card_with_image() { 1087 info("HighlightsFeed.fetchImage should not update the card with the image"); 1088 let sandbox = sinon.createSandbox(); 1089 let feed = getHighlightsFeedForTest(sandbox); 1090 1091 sandbox.stub(SectionsManager, "updateSectionCard"); 1092 sandbox.stub(Screenshots, "getScreenshotForURL").resolves(FAKE_IMAGE); 1093 sandbox.stub(Screenshots, "_shouldGetScreenshots").returns(true); 1094 1095 let card = { 1096 preview_image_url: FAKE_IMAGE_URL, 1097 url: FAKE_URL, 1098 }; 1099 await fetchImage(feed, card); 1100 Assert.ok(!card.image, "Image not set on card"); 1101 sandbox.restore(); 1102 }); 1103 1104 add_task(async function test_uninit_disable_section() { 1105 info("HighlightsFeed.onAction(UNINIT) should disable its section"); 1106 let sandbox = sinon.createSandbox(); 1107 let feed = getHighlightsFeedForTest(sandbox); 1108 feed.init(); 1109 1110 sandbox.stub(SectionsManager, "disableSection"); 1111 feed.onAction({ type: actionTypes.UNINIT }); 1112 Assert.ok( 1113 SectionsManager.disableSection.calledOnce, 1114 "SectionsManager.disableSection called" 1115 ); 1116 Assert.ok(SectionsManager.disableSection.calledWith(SECTION_ID)); 1117 sandbox.restore(); 1118 }); 1119 1120 add_task(async function test_uninit_remove_expiration_filter() { 1121 info("HighlightsFeed.onAction(UNINIT) should remove the expiration filter"); 1122 let sandbox = sinon.createSandbox(); 1123 let feed = getHighlightsFeedForTest(sandbox); 1124 feed.init(); 1125 1126 sandbox.stub(PageThumbs, "removeExpirationFilter"); 1127 feed.onAction({ type: actionTypes.UNINIT }); 1128 Assert.ok( 1129 PageThumbs.removeExpirationFilter.calledOnce, 1130 "PageThumbs.removeExpirationFilter called" 1131 ); 1132 1133 sandbox.restore(); 1134 }); 1135 1136 add_task(async function test_onAction_relay_to_DownloadsManager_onAction() { 1137 info( 1138 "HighlightsFeed.onAction should relay all actions to " + 1139 "DownloadsManager.onAction" 1140 ); 1141 let sandbox = sinon.createSandbox(); 1142 let feed = getHighlightsFeedForTest(sandbox); 1143 sandbox.stub(feed.downloadsManager, "onAction"); 1144 1145 let action = { 1146 type: actionTypes.COPY_DOWNLOAD_LINK, 1147 data: { url: "foo.png" }, 1148 _target: {}, 1149 }; 1150 feed.onAction(action); 1151 1152 Assert.ok( 1153 feed.downloadsManager.onAction.calledOnce, 1154 "HighlightsFeed.downloadManager.onAction called" 1155 ); 1156 Assert.ok(feed.downloadsManager.onAction.calledWith(action)); 1157 sandbox.restore(); 1158 }); 1159 1160 add_task(async function test_onAction_fetch_highlights_on_SYSTEM_TICK() { 1161 info("HighlightsFeed.onAction should fetch highlights on SYSTEM_TICK"); 1162 let sandbox = sinon.createSandbox(); 1163 let feed = getHighlightsFeedForTest(sandbox); 1164 1165 await feed.fetchHighlights(); 1166 1167 sandbox.spy(feed, "fetchHighlights"); 1168 feed.onAction({ type: actionTypes.SYSTEM_TICK }); 1169 1170 Assert.ok( 1171 feed.fetchHighlights.calledOnce, 1172 "HighlightsFeed.fetchHighlights called" 1173 ); 1174 Assert.ok( 1175 feed.fetchHighlights.calledWithExactly({ 1176 broadcast: false, 1177 isStartup: false, 1178 }) 1179 ); 1180 sandbox.restore(); 1181 }); 1182 1183 add_task( 1184 async function test_onAction_fetch_highlights_on_PREF_CHANGED_for_include() { 1185 info( 1186 "HighlightsFeed.onAction should fetch highlights on PREF_CHANGED " + 1187 "for include prefs" 1188 ); 1189 let sandbox = sinon.createSandbox(); 1190 let feed = getHighlightsFeedForTest(sandbox); 1191 1192 sandbox.spy(feed, "fetchHighlights"); 1193 feed.onAction({ 1194 type: actionTypes.PREF_CHANGED, 1195 data: { name: "section.highlights.includeBookmarks" }, 1196 }); 1197 1198 Assert.ok( 1199 feed.fetchHighlights.calledOnce, 1200 "HighlightsFeed.fetchHighlights called" 1201 ); 1202 Assert.ok(feed.fetchHighlights.calledWithExactly({ broadcast: true })); 1203 sandbox.restore(); 1204 } 1205 ); 1206 1207 add_task( 1208 async function test_onAction_no_fetch_highlights_on_PREF_CHANGED_for_other() { 1209 info( 1210 "HighlightsFeed.onAction should not fetch highlights on PREF_CHANGED " + 1211 "for other prefs" 1212 ); 1213 let sandbox = sinon.createSandbox(); 1214 let feed = getHighlightsFeedForTest(sandbox); 1215 1216 sandbox.spy(feed, "fetchHighlights"); 1217 feed.onAction({ 1218 type: actionTypes.PREF_CHANGED, 1219 data: { name: "section.topstories.pocketCta" }, 1220 }); 1221 1222 Assert.ok( 1223 feed.fetchHighlights.notCalled, 1224 "HighlightsFeed.fetchHighlights not called" 1225 ); 1226 1227 sandbox.restore(); 1228 } 1229 ); 1230 1231 add_task(async function test_onAction_fetch_highlights_on_actions() { 1232 info("HighlightsFeed.onAction should fetch highlights for various actions"); 1233 1234 let actions = [ 1235 { 1236 actionType: "PLACES_HISTORY_CLEARED", 1237 expectsExpire: false, 1238 expectsBroadcast: true, 1239 }, 1240 { 1241 actionType: "DOWNLOAD_CHANGED", 1242 expectsExpire: false, 1243 expectsBroadcast: true, 1244 }, 1245 { 1246 actionType: "PLACES_LINKS_CHANGED", 1247 expectsExpire: true, 1248 expectsBroadcast: false, 1249 }, 1250 { 1251 actionType: "PLACES_LINK_BLOCKED", 1252 expectsExpire: false, 1253 expectsBroadcast: true, 1254 }, 1255 ]; 1256 for (let action of actions) { 1257 info( 1258 `HighlightsFeed.onAction should fetch highlights on ${action.actionType}` 1259 ); 1260 let sandbox = sinon.createSandbox(); 1261 let feed = getHighlightsFeedForTest(sandbox); 1262 1263 await feed.fetchHighlights(); 1264 sandbox.spy(feed, "fetchHighlights"); 1265 sandbox.stub(feed.linksCache, "expire"); 1266 1267 feed.onAction({ type: actionTypes[action.actionType] }); 1268 Assert.ok( 1269 feed.fetchHighlights.calledOnce, 1270 "HighlightsFeed.fetchHighlights called" 1271 ); 1272 Assert.ok( 1273 feed.fetchHighlights.calledWith({ broadcast: action.expectsBroadcast }) 1274 ); 1275 1276 if (action.expectsExpire) { 1277 Assert.ok( 1278 feed.linksCache.expire.calledOnce, 1279 "HighlightsFeed.linksCache.expire called" 1280 ); 1281 } 1282 1283 sandbox.restore(); 1284 } 1285 }); 1286 1287 add_task( 1288 async function test_onAction_fetch_highlights_no_broadcast_on_TOP_SITES_UPDATED() { 1289 info( 1290 "HighlightsFeed.onAction should fetch highlights with broadcast " + 1291 "false on TOP_SITES_UPDATED" 1292 ); 1293 1294 let sandbox = sinon.createSandbox(); 1295 let feed = getHighlightsFeedForTest(sandbox); 1296 1297 await feed.fetchHighlights(); 1298 sandbox.spy(feed, "fetchHighlights"); 1299 1300 feed.onAction({ type: actionTypes.TOP_SITES_UPDATED }); 1301 Assert.ok( 1302 feed.fetchHighlights.calledOnce, 1303 "HighlightsFeed.fetchHighlights called" 1304 ); 1305 Assert.ok( 1306 feed.fetchHighlights.calledWithExactly({ 1307 broadcast: false, 1308 isStartup: false, 1309 }) 1310 ); 1311 1312 sandbox.restore(); 1313 } 1314 );