test_top_sites.js (74793B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { TopSites, insertPinned, DEFAULT_TOP_SITES } = 7 ChromeUtils.importESModule("resource:///modules/topsites/TopSites.sys.mjs"); 8 9 ChromeUtils.defineESModuleGetters(this, { 10 FilterAdult: "resource:///modules/FilterAdult.sys.mjs", 11 NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs", 12 NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", 13 sinon: "resource://testing-common/Sinon.sys.mjs", 14 PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs", 15 PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", 16 SearchService: "resource://gre/modules/SearchService.sys.mjs", 17 TestUtils: "resource://testing-common/TestUtils.sys.mjs", 18 TOP_SITES_DEFAULT_ROWS: "resource:///modules/topsites/constants.mjs", 19 TOP_SITES_MAX_SITES_PER_ROW: "resource:///modules/topsites/constants.mjs", 20 }); 21 22 const FAKE_FAVICON = "data987"; 23 const FAKE_FAVICON_SIZE = 128; 24 // Two visits on the same day. 25 const FAKE_FRECENCY = PlacesUtils.history.pageFrecencyThreshold(0, 2, false); 26 const FAKE_LINKS = new Array(2 * TOP_SITES_MAX_SITES_PER_ROW) 27 .fill(null) 28 .map((v, i) => ({ 29 frecency: FAKE_FRECENCY, 30 url: `http://www.site${i}.com`, 31 })); 32 33 function FakeTippyTopProvider() {} 34 FakeTippyTopProvider.prototype = { 35 async init() { 36 this.initialized = true; 37 }, 38 processSite(site) { 39 return site; 40 }, 41 }; 42 43 let gSearchServiceInitStub; 44 let gGetTopSitesStub; 45 46 function stubTopSites(sandbox) { 47 async function cleanup() { 48 if (TopSites._refreshing) { 49 info("Wait for refresh to finish."); 50 // Wait for refresh to finish or else removing the store while a process 51 // is running will result in errors. 52 await TestUtils.topicObserved("topsites-refreshed"); 53 info("Top sites was refreshed."); 54 } 55 TopSites._tippyTopProvider.initialized = false; 56 TopSites.pinnedCache.clear(); 57 TopSites.frecentCache.clear(); 58 TopSites._reset(); 59 stub.restore(); 60 info("Finished cleaning up TopSites."); 61 } 62 63 // To avoid having to setup search for each test, we stub this method and 64 // unstub it when the unit test calls for the search shortcuts. 65 let stub = sandbox.stub(TopSites, "updateCustomSearchShortcuts"); 66 67 TopSites._requestRichIcon = sandbox.stub(); 68 // Set preferences to match the store state. 69 Services.prefs.setIntPref( 70 "browser.newtabpage.activity-stream.topSitesRows", 71 2 72 ); 73 info("Created mock store for TopSites."); 74 return cleanup; 75 } 76 77 function createExpectedPinnedLink(link, index) { 78 link.isDefault = false; 79 link.isPinned = true; 80 link.searchTopSite = false; 81 link.favicon = FAKE_FAVICON; 82 link.faviconSize = FAKE_FAVICON_SIZE; 83 link.pinIndex = index; 84 return link; 85 } 86 87 function assertLinks(actualLinks, expectedLinks) { 88 Assert.equal( 89 actualLinks.length, 90 expectedLinks.length, 91 "Links have equal length." 92 ); 93 for (let i = 0; i < actualLinks.length; ++i) { 94 Assert.deepEqual(actualLinks[i], expectedLinks[i], "Link entry matches"); 95 } 96 } 97 98 add_setup(async () => { 99 // Places requires a profile. 100 do_get_profile(); 101 102 let sandbox = sinon.createSandbox(); 103 sandbox.stub(SearchService.prototype, "defaultEngine").get(() => { 104 return { identifier: "ddg", searchUrlDomain: "duckduckgo.com" }; 105 }); 106 107 gGetTopSitesStub = sandbox 108 .stub(NewTabUtils.activityStreamLinks, "getTopSites") 109 .resolves(FAKE_LINKS); 110 111 gSearchServiceInitStub = sandbox 112 .stub(SearchService.prototype, "init") 113 .resolves(); 114 115 sandbox.stub(NewTabUtils.activityStreamProvider, "_faviconBytesToDataURI"); 116 117 sandbox 118 .stub(NewTabUtils.activityStreamProvider, "_addFavicons") 119 .callsFake(l => { 120 return Promise.resolve( 121 l.map(link => { 122 link.favicon = FAKE_FAVICON; 123 link.faviconSize = FAKE_FAVICON_SIZE; 124 return link; 125 }) 126 ); 127 }); 128 129 registerCleanupFunction(() => { 130 sandbox.restore(); 131 }); 132 }); 133 134 add_task(async function test_construction() { 135 Assert.ok(TopSites._currentSearchHostname, "_currentSearchHostname defined"); 136 }); 137 138 add_task(async function test_refreshDefaults() { 139 let sandbox = sinon.createSandbox(); 140 let cleanup = stubTopSites(sandbox); 141 Assert.ok( 142 !DEFAULT_TOP_SITES.length, 143 "Should have 0 DEFAULT_TOP_SITES initially." 144 ); 145 146 // We have to init to subscribe to changes to the preferences. 147 await TopSites.init(); 148 149 info( 150 "TopSites.refreshDefaults should add defaults on default.sites pref change." 151 ); 152 Services.prefs.setStringPref( 153 "browser.newtabpage.activity-stream.default.sites", 154 "https://foo.com" 155 ); 156 157 Assert.equal( 158 DEFAULT_TOP_SITES.length, 159 1, 160 "Should have 1 DEFAULT_TOP_SITES now." 161 ); 162 163 // Reset the DEFAULT_TOP_SITES; 164 DEFAULT_TOP_SITES.length = 0; 165 166 info("refreshDefaults should refresh on topSiteRows PREF_CHANGED"); 167 let refreshStub = sandbox.stub(TopSites, "refresh"); 168 Services.prefs.setIntPref( 169 "browser.newtabpage.activity-stream.topSitesRows", 170 1 171 ); 172 Assert.ok(TopSites.refresh.calledOnce, "refresh called"); 173 refreshStub.restore(); 174 175 // Reset the DEFAULT_TOP_SITES; 176 DEFAULT_TOP_SITES.length = 0; 177 178 info("refreshDefaults should have default sites with .isDefault = true"); 179 TopSites.refreshDefaults("https://foo.com"); 180 Assert.equal( 181 DEFAULT_TOP_SITES.length, 182 1, 183 "Should have a DEFAULT_TOP_SITES now." 184 ); 185 Assert.ok( 186 DEFAULT_TOP_SITES[0].isDefault, 187 "Lone top site should be the default." 188 ); 189 190 // Reset the DEFAULT_TOP_SITES; 191 DEFAULT_TOP_SITES.length = 0; 192 193 info("refreshDefaults should have default sites with appropriate hostname"); 194 TopSites.refreshDefaults("https://foo.com"); 195 Assert.equal( 196 DEFAULT_TOP_SITES.length, 197 1, 198 "Should have a DEFAULT_TOP_SITES now." 199 ); 200 let [site] = DEFAULT_TOP_SITES; 201 Assert.equal( 202 site.hostname, 203 NewTabUtils.shortURL(site), 204 "Lone top site should have the right hostname." 205 ); 206 207 // Reset the DEFAULT_TOP_SITES; 208 DEFAULT_TOP_SITES.length = 0; 209 210 info("refreshDefaults should add no defaults on empty pref"); 211 TopSites.refreshDefaults(""); 212 Assert.equal( 213 DEFAULT_TOP_SITES.length, 214 0, 215 "Should have 0 DEFAULT_TOP_SITES now." 216 ); 217 218 info("refreshDefaults should be able to clear defaults"); 219 TopSites.refreshDefaults("https://foo.com"); 220 TopSites.refreshDefaults(""); 221 222 Assert.equal( 223 DEFAULT_TOP_SITES.length, 224 0, 225 "Should have 0 DEFAULT_TOP_SITES now." 226 ); 227 228 Services.prefs.clearUserPref( 229 "browser.newtabpage.activity-stream.default.sites" 230 ); 231 Services.prefs.clearUserPref( 232 "browser.newtabpage.activity-stream.topSitesRows" 233 ); 234 TopSites.uninit(); 235 sandbox.restore(); 236 await cleanup(); 237 }); 238 239 add_task( 240 async function test_getLinksWithDefaults_on_SearchService_init_failure() { 241 let sandbox = sinon.createSandbox(); 242 let cleanup = stubTopSites(sandbox); 243 244 TopSites.refreshDefaults("https://foo.com"); 245 246 gSearchServiceInitStub.rejects( 247 new Error("Simulating search init failures") 248 ); 249 250 const result = await TopSites.getLinksWithDefaults(); 251 Assert.ok(result); 252 253 gSearchServiceInitStub.resolves(); 254 255 sandbox.restore(); 256 await cleanup(); 257 } 258 ); 259 260 add_task(async function test_getLinksWithDefaults() { 261 NewTabUtils.activityStreamLinks.getTopSites.resetHistory(); 262 263 let sandbox = sinon.createSandbox(); 264 let cleanup = stubTopSites(sandbox); 265 266 TopSites.refreshDefaults("https://foo.com"); 267 268 info("getLinksWithDefaults should get the links from NewTabUtils"); 269 let result = await TopSites.getLinksWithDefaults(); 270 271 const reference = FAKE_LINKS.map(site => 272 Object.assign({}, site, { 273 hostname: NewTabUtils.shortURL(site), 274 typedBonus: true, 275 }) 276 ); 277 278 Assert.deepEqual(result, reference); 279 Assert.ok(NewTabUtils.activityStreamLinks.getTopSites.calledOnce); 280 281 info("getLinksWithDefaults should indicate the links get typed bonus"); 282 Assert.ok(result[0].typedBonus, "Expected typed bonus property to be true."); 283 284 sandbox.restore(); 285 await cleanup(); 286 }); 287 288 add_task(async function test_getLinksWithDefaults_filterAdult() { 289 let sandbox = sinon.createSandbox(); 290 info("getLinksWithDefaults should filter out non-pinned adult sites"); 291 292 sandbox.stub(FilterAdult, "filter").returns([]); 293 const TEST_URL = "https://foo.com/"; 294 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [{ url: TEST_URL }]); 295 296 let cleanup = stubTopSites(sandbox); 297 TopSites.refreshDefaults("https://foo.com"); 298 299 const result = await TopSites.getLinksWithDefaults(); 300 Assert.ok(FilterAdult.filter.calledOnce); 301 Assert.equal(result.length, 1); 302 Assert.equal(result[0].url, TEST_URL); 303 304 sandbox.restore(); 305 await cleanup(); 306 }); 307 308 add_task(async function test_getLinksWithDefaults_caching() { 309 let sandbox = sinon.createSandbox(); 310 311 info( 312 "getLinksWithDefaults should filter out the defaults that have been blocked" 313 ); 314 // make sure we only have one top site, and we block the only default site we have to show 315 const url = "www.myonlytopsite.com"; 316 const topsite = { 317 frecency: FAKE_FRECENCY, 318 hostname: NewTabUtils.shortURL({ url }), 319 typedBonus: true, 320 url, 321 }; 322 323 const blockedDefaultSite = { url: "https://foo.com" }; 324 gGetTopSitesStub.resolves([topsite]); 325 sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => { 326 return site.url === blockedDefaultSite.url; 327 }); 328 329 let cleanup = stubTopSites(sandbox); 330 TopSites.refreshDefaults("https://foo.com"); 331 const result = await TopSites.getLinksWithDefaults(); 332 333 // what we should be left with is just the top site we added, and not the default site we blocked 334 Assert.equal(result.length, 1); 335 Assert.deepEqual(result[0], topsite); 336 let foundBlocked = result.find(site => site.url === blockedDefaultSite.url); 337 Assert.ok(!foundBlocked, "Should not have found blocked site."); 338 339 gGetTopSitesStub.resolves(FAKE_LINKS); 340 sandbox.restore(); 341 await cleanup(); 342 }); 343 344 add_task(async function test_getLinksWithDefaults_dedupe() { 345 let sandbox = sinon.createSandbox(); 346 347 info("getLinksWithDefaults should call dedupe.group on the links"); 348 let cleanup = stubTopSites(sandbox); 349 TopSites.refreshDefaults("https://foo.com"); 350 351 let stub = sandbox.stub(TopSites.dedupe, "group").callsFake((...id) => id); 352 await TopSites.getLinksWithDefaults(); 353 354 Assert.ok(stub.calledOnce, "dedupe.group was called once"); 355 sandbox.restore(); 356 await cleanup(); 357 }); 358 359 add_task(async function test__dedupe_key() { 360 let sandbox = sinon.createSandbox(); 361 362 info("_dedupeKey should dedupe on hostname instead of url"); 363 let cleanup = stubTopSites(sandbox); 364 TopSites.refreshDefaults("https://foo.com"); 365 366 let site = { url: "foo", hostname: "bar" }; 367 let result = TopSites._dedupeKey(site); 368 369 Assert.equal(result, site.hostname, "deduped on hostname"); 370 sandbox.restore(); 371 await cleanup(); 372 }); 373 374 add_task(async function test_getLinksWithDefaults_adds_defaults() { 375 let sandbox = sinon.createSandbox(); 376 377 info( 378 "getLinksWithDefaults should add defaults if there are are not enough links" 379 ); 380 const TEST_LINKS = [{ frecency: FAKE_FRECENCY, url: "foo.com" }]; 381 gGetTopSitesStub.resolves(TEST_LINKS); 382 let cleanup = stubTopSites(sandbox); 383 TopSites.refreshDefaults("https://foo.com"); 384 385 let result = await TopSites.getLinksWithDefaults(); 386 387 let reference = [...TEST_LINKS, ...DEFAULT_TOP_SITES].map(s => 388 Object.assign({}, s, { 389 hostname: NewTabUtils.shortURL(s), 390 typedBonus: true, 391 }) 392 ); 393 394 Assert.deepEqual(result, reference); 395 396 gGetTopSitesStub.resolves(FAKE_LINKS); 397 sandbox.restore(); 398 await cleanup(); 399 }); 400 401 add_task( 402 async function test_getLinksWithDefaults_adds_defaults_for_visible_slots() { 403 let sandbox = sinon.createSandbox(); 404 405 info( 406 "getLinksWithDefaults should only add defaults up to the number of visible slots" 407 ); 408 const numVisible = TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW; 409 let testLinks = []; 410 for (let i = 0; i < numVisible - 1; i++) { 411 testLinks.push({ frecency: FAKE_FRECENCY, url: `foo${i}.com` }); 412 } 413 gGetTopSitesStub.resolves(testLinks); 414 415 let cleanup = stubTopSites(sandbox); 416 TopSites.refreshDefaults("https://foo.com"); 417 418 let result = await TopSites.getLinksWithDefaults(); 419 420 let reference = [...testLinks, DEFAULT_TOP_SITES[0]].map(s => 421 Object.assign({}, s, { 422 hostname: NewTabUtils.shortURL(s), 423 typedBonus: true, 424 }) 425 ); 426 427 Assert.equal(result.length, numVisible); 428 Assert.deepEqual(result, reference); 429 430 gGetTopSitesStub.resolves(FAKE_LINKS); 431 sandbox.restore(); 432 await cleanup(); 433 } 434 ); 435 436 add_task(async function test_getLinksWithDefaults_no_throw_on_no_links() { 437 let sandbox = sinon.createSandbox(); 438 439 info("getLinksWithDefaults should not throw if NewTabUtils returns null"); 440 gGetTopSitesStub.resolves(null); 441 442 let cleanup = stubTopSites(sandbox); 443 TopSites.refreshDefaults("https://foo.com"); 444 445 await TopSites.getLinksWithDefaults(); 446 Assert.ok(true, "getLinksWithDefaults did not throw"); 447 448 gGetTopSitesStub.resolves(FAKE_LINKS); 449 sandbox.restore(); 450 await cleanup(); 451 }); 452 453 add_task(async function test_getLinksWithDefaults_get_more_on_request() { 454 let sandbox = sinon.createSandbox(); 455 456 info("getLinksWithDefaults should get more if the user has asked for more"); 457 let testLinks = new Array(4 * TOP_SITES_MAX_SITES_PER_ROW) 458 .fill(null) 459 .map((v, i) => ({ 460 frecency: FAKE_FRECENCY, 461 url: `http://www.site${i}.com`, 462 })); 463 gGetTopSitesStub.resolves(testLinks); 464 465 let cleanup = stubTopSites(sandbox); 466 TopSites.refreshDefaults("https://foo.com"); 467 468 const TEST_ROWS = 3; 469 Services.prefs.setIntPref( 470 "browser.newtabpage.activity-stream.topSitesRows", 471 TEST_ROWS 472 ); 473 474 let result = await TopSites.getLinksWithDefaults(); 475 Assert.equal(result.length, TEST_ROWS * TOP_SITES_MAX_SITES_PER_ROW); 476 477 Services.prefs.clearUserPref( 478 "browser.newtabpage.activity-stream.topSitesRows" 479 ); 480 gGetTopSitesStub.resolves(FAKE_LINKS); 481 sandbox.restore(); 482 await cleanup(); 483 }); 484 485 add_task(async function test_getLinksWithDefaults_reuse_cache() { 486 let sandbox = sinon.createSandbox(); 487 info("getLinksWithDefaults should reuse the cache on subsequent calls"); 488 489 let cleanup = stubTopSites(sandbox); 490 TopSites.refreshDefaults("https://foo.com"); 491 492 gGetTopSitesStub.resetHistory(); 493 494 await TopSites.getLinksWithDefaults(); 495 await TopSites.getLinksWithDefaults(); 496 497 Assert.ok( 498 NewTabUtils.activityStreamLinks.getTopSites.calledOnce, 499 "getTopSites only called once" 500 ); 501 502 sandbox.restore(); 503 await cleanup(); 504 }); 505 506 add_task( 507 async function test_getLinksWithDefaults_ignore_cache_on_requesting_more() { 508 let sandbox = sinon.createSandbox(); 509 info("getLinksWithDefaults should ignore the cache when requesting more"); 510 511 let cleanup = stubTopSites(sandbox); 512 TopSites.refreshDefaults("https://foo.com"); 513 514 gGetTopSitesStub.resetHistory(); 515 516 await TopSites.getLinksWithDefaults(); 517 Services.prefs.setIntPref( 518 "browser.newtabpage.activity-stream.topSitesRows", 519 3 520 ); 521 await TopSites.getLinksWithDefaults(); 522 523 Assert.ok( 524 NewTabUtils.activityStreamLinks.getTopSites.calledTwice, 525 "getTopSites called twice" 526 ); 527 528 Services.prefs.clearUserPref( 529 "browser.newtabpage.activity-stream.topSitesRows" 530 ); 531 sandbox.restore(); 532 await cleanup(); 533 } 534 ); 535 536 add_task( 537 async function test_getLinksWithDefaults_migrate_pinned_favicon_data() { 538 let sandbox = sinon.createSandbox(); 539 info( 540 "getLinksWithDefaults should migrate pinned favicon data without getting favicons again" 541 ); 542 543 let cleanup = stubTopSites(sandbox); 544 TopSites.refreshDefaults("https://foo.com"); 545 546 gGetTopSitesStub.resetHistory(); 547 548 sandbox 549 .stub(NewTabUtils.pinnedLinks, "links") 550 .get(() => [{ url: "https://foo.com/" }]); 551 552 await TopSites.getLinksWithDefaults(); 553 554 let originalCallCount = 555 NewTabUtils.activityStreamProvider._addFavicons.callCount; 556 TopSites.pinnedCache.expire(); 557 558 let result = await TopSites.getLinksWithDefaults(); 559 560 Assert.equal( 561 NewTabUtils.activityStreamProvider._addFavicons.callCount, 562 originalCallCount, 563 "_addFavicons was not called again." 564 ); 565 Assert.equal(result[0].favicon, FAKE_FAVICON); 566 Assert.equal(result[0].faviconSize, FAKE_FAVICON_SIZE); 567 568 sandbox.restore(); 569 await cleanup(); 570 } 571 ); 572 573 add_task(async function test_getLinksWithDefaults_no_internal_properties() { 574 let sandbox = sinon.createSandbox(); 575 info("getLinksWithDefaults should not expose internal link properties"); 576 577 let cleanup = stubTopSites(sandbox); 578 TopSites.refreshDefaults("https://foo.com"); 579 580 let result = await TopSites.getLinksWithDefaults(); 581 582 let internal = Object.keys(result[0]).filter(key => key.startsWith("__")); 583 Assert.equal(internal.join(""), ""); 584 585 sandbox.restore(); 586 await cleanup(); 587 }); 588 589 add_task(async function test_getLinksWithDefaults_copy_frecent_screenshot() { 590 // TopSites pulls data from NewTabUtils.activityStreamLinks.getTopSites() 591 // which can still pass screenshots to it if they are available. 592 let sandbox = sinon.createSandbox(); 593 info( 594 "getLinksWithDefaults should copy the screenshot of the frecent site if it exists" 595 ); 596 597 let cleanup = stubTopSites(sandbox); 598 TopSites.refreshDefaults("https://foo.com"); 599 600 const TEST_SCREENSHOT = "screenshot"; 601 602 gGetTopSitesStub.resolves([ 603 { url: "https://foo.com/", screenshot: TEST_SCREENSHOT }, 604 ]); 605 sandbox 606 .stub(NewTabUtils.pinnedLinks, "links") 607 .get(() => [{ url: "https://foo.com/" }]); 608 609 let result = await TopSites.getLinksWithDefaults(); 610 611 Assert.equal(result[0].screenshot, TEST_SCREENSHOT); 612 613 gGetTopSitesStub.resolves(FAKE_LINKS); 614 sandbox.restore(); 615 await cleanup(); 616 }); 617 618 add_task(async function test_getLinksWithDefaults_copies_both_screenshots() { 619 // TopSites pulls data from NewTabUtils.activityStreamLinks.getTopSites() 620 // and NewTabUtils.pinnedLinks which can pass screenshot data to it if they 621 // are available. 622 let sandbox = sinon.createSandbox(); 623 info( 624 "getLinksWithDefaults should still copy the frecent screenshot if " + 625 "customScreenshotURL is set" 626 ); 627 628 let cleanup = stubTopSites(sandbox); 629 TopSites.refreshDefaults("https://foo.com"); 630 631 gGetTopSitesStub.resolves([ 632 { url: "https://foo.com/", screenshot: "screenshot" }, 633 ]); 634 sandbox 635 .stub(NewTabUtils.pinnedLinks, "links") 636 .get(() => [{ url: "https://foo.com/", customScreenshotURL: "custom" }]); 637 638 let result = await TopSites.getLinksWithDefaults(); 639 640 Assert.equal(result[0].screenshot, "screenshot"); 641 Assert.equal(result[0].customScreenshotURL, "custom"); 642 643 gGetTopSitesStub.resolves(FAKE_LINKS); 644 sandbox.restore(); 645 await cleanup(); 646 }); 647 648 add_task(async function test_getLinksWithDefaults_persist_screenshot() { 649 let sandbox = sinon.createSandbox(); 650 info( 651 "getLinksWithDefaults should keep the same screenshot if no frecent site is found" 652 ); 653 654 let cleanup = stubTopSites(sandbox); 655 TopSites.refreshDefaults("https://foo.com"); 656 657 const CUSTOM_SCREENSHOT = "custom"; 658 659 gGetTopSitesStub.resolves([]); 660 sandbox 661 .stub(NewTabUtils.pinnedLinks, "links") 662 .get(() => [{ url: "https://foo.com/", screenshot: CUSTOM_SCREENSHOT }]); 663 664 let result = await TopSites.getLinksWithDefaults(); 665 666 Assert.equal(result[0].screenshot, CUSTOM_SCREENSHOT); 667 668 gGetTopSitesStub.resolves(FAKE_LINKS); 669 sandbox.restore(); 670 await cleanup(); 671 }); 672 673 add_task( 674 async function test_getLinksWithDefaults_no_overwrite_pinned_screenshot() { 675 let sandbox = sinon.createSandbox(); 676 info("getLinksWithDefaults should not overwrite pinned site screenshot"); 677 678 let cleanup = stubTopSites(sandbox); 679 TopSites.refreshDefaults("https://foo.com"); 680 681 const EXISTING_SCREENSHOT = "some-screenshot"; 682 683 gGetTopSitesStub.resolves([{ url: "https://foo.com/", screenshot: "foo" }]); 684 sandbox 685 .stub(NewTabUtils.pinnedLinks, "links") 686 .get(() => [ 687 { url: "https://foo.com/", screenshot: EXISTING_SCREENSHOT }, 688 ]); 689 690 let result = await TopSites.getLinksWithDefaults(); 691 692 Assert.equal(result[0].screenshot, EXISTING_SCREENSHOT); 693 694 gGetTopSitesStub.resolves(FAKE_LINKS); 695 sandbox.restore(); 696 await cleanup(); 697 } 698 ); 699 700 add_task( 701 async function test_getLinksWithDefaults_no_searchTopSite_from_frecent() { 702 let sandbox = sinon.createSandbox(); 703 info("getLinksWithDefaults should not set searchTopSite from frecent site"); 704 705 let cleanup = stubTopSites(sandbox); 706 TopSites.refreshDefaults("https://foo.com"); 707 708 const EXISTING_SCREENSHOT = "some-screenshot"; 709 710 gGetTopSitesStub.resolves([ 711 { 712 url: "https://foo.com/", 713 searchTopSite: true, 714 screenshot: EXISTING_SCREENSHOT, 715 }, 716 ]); 717 sandbox 718 .stub(NewTabUtils.pinnedLinks, "links") 719 .get(() => [{ url: "https://foo.com/" }]); 720 721 let result = await TopSites.getLinksWithDefaults(); 722 723 Assert.ok(!result[0].searchTopSite); 724 // But it should copy over other properties 725 Assert.equal(result[0].screenshot, EXISTING_SCREENSHOT); 726 727 gGetTopSitesStub.resolves(FAKE_LINKS); 728 sandbox.restore(); 729 await cleanup(); 730 } 731 ); 732 733 add_task(async function test_getLinksWithDefaults_concurrency_getTopSites() { 734 let sandbox = sinon.createSandbox(); 735 info( 736 "getLinksWithDefaults concurrent calls should call the backing data once" 737 ); 738 739 let cleanup = stubTopSites(sandbox); 740 TopSites.refreshDefaults("https://foo.com"); 741 742 NewTabUtils.activityStreamLinks.getTopSites.resetHistory(); 743 744 await Promise.all([ 745 TopSites.getLinksWithDefaults(), 746 TopSites.getLinksWithDefaults(), 747 ]); 748 749 Assert.ok( 750 NewTabUtils.activityStreamLinks.getTopSites.calledOnce, 751 "getTopSites only called once" 752 ); 753 754 sandbox.restore(); 755 await cleanup(); 756 }); 757 758 add_task(async function test_getLinksWithDefaults_deduping_no_dedupe_pinned() { 759 let sandbox = sinon.createSandbox(); 760 info("getLinksWithDefaults should not dedupe pinned sites"); 761 762 let cleanup = stubTopSites(sandbox); 763 TopSites.refreshDefaults("https://foo.com"); 764 765 sandbox 766 .stub(NewTabUtils.pinnedLinks, "links") 767 .get(() => [ 768 { url: "https://developer.mozilla.org/en-US/docs/Web" }, 769 { url: "https://developer.mozilla.org/en-US/docs/Learn" }, 770 ]); 771 772 let sites = await TopSites.getLinksWithDefaults(); 773 Assert.equal(sites.length, 2 * TOP_SITES_MAX_SITES_PER_ROW); 774 Assert.equal(sites[0].url, NewTabUtils.pinnedLinks.links[0].url); 775 Assert.equal(sites[1].url, NewTabUtils.pinnedLinks.links[1].url); 776 Assert.equal(sites[0].hostname, sites[1].hostname); 777 778 sandbox.restore(); 779 await cleanup(); 780 }); 781 782 add_task(async function test_getLinksWithDefaults_prefer_pinned_sites() { 783 let sandbox = sinon.createSandbox(); 784 785 info("getLinksWithDefaults should prefer pinned sites over links"); 786 787 let cleanup = stubTopSites(sandbox); 788 TopSites.refreshDefaults(); 789 790 sandbox 791 .stub(NewTabUtils.pinnedLinks, "links") 792 .get(() => [ 793 { url: "https://developer.mozilla.org/en-US/docs/Web" }, 794 { url: "https://developer.mozilla.org/en-US/docs/Learn" }, 795 ]); 796 797 const SECOND_TOP_SITE_URL = "https://www.mozilla.org/"; 798 799 gGetTopSitesStub.resolves([ 800 { frecency: FAKE_FRECENCY, url: "https://developer.mozilla.org/" }, 801 { frecency: FAKE_FRECENCY, url: SECOND_TOP_SITE_URL }, 802 ]); 803 804 let sites = await TopSites.getLinksWithDefaults(); 805 806 // Expecting 3 links where there's 2 pinned and 1 www.mozilla.org, so 807 // the frecent with matching hostname as pinned is removed. 808 Assert.equal(sites.length, 3); 809 Assert.equal(sites[0].url, NewTabUtils.pinnedLinks.links[0].url); 810 Assert.equal(sites[1].url, NewTabUtils.pinnedLinks.links[1].url); 811 Assert.equal(sites[2].url, SECOND_TOP_SITE_URL); 812 813 gGetTopSitesStub.resolves(FAKE_LINKS); 814 sandbox.restore(); 815 await cleanup(); 816 }); 817 818 add_task(async function test_getLinksWithDefaults_title_and_null() { 819 let sandbox = sinon.createSandbox(); 820 821 info("getLinksWithDefaults should return sites that have a title"); 822 823 let cleanup = stubTopSites(sandbox); 824 TopSites.refreshDefaults(); 825 826 sandbox 827 .stub(NewTabUtils.pinnedLinks, "links") 828 .get(() => [{ url: "https://github.com/mozilla/activity-stream" }]); 829 830 let sites = await TopSites.getLinksWithDefaults(); 831 for (let site of sites) { 832 Assert.ok(site.hostname); 833 } 834 835 info("getLinksWithDefaults should not throw for null entries"); 836 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [null]); 837 await TopSites.getLinksWithDefaults(); 838 Assert.ok(true, "getLinksWithDefaults didn't throw"); 839 840 sandbox.restore(); 841 await cleanup(); 842 }); 843 844 add_task(async function test_getLinksWithDefaults_calls__fetchIcon() { 845 let sandbox = sinon.createSandbox(); 846 847 info("getLinksWithDefaults should return sites that have a title"); 848 849 let cleanup = stubTopSites(sandbox); 850 TopSites.refreshDefaults(); 851 852 sandbox.spy(TopSites, "_fetchIcon"); 853 let results = await TopSites.getLinksWithDefaults(); 854 Assert.ok(results.length, "Got back some results"); 855 Assert.equal(TopSites._fetchIcon.callCount, results.length); 856 for (let result of results) { 857 Assert.ok(TopSites._fetchIcon.calledWith(result)); 858 } 859 860 sandbox.restore(); 861 await cleanup(); 862 }); 863 864 add_task(async function test_init() { 865 let sandbox = sinon.createSandbox(); 866 867 sandbox.stub(NimbusFeatures.newtab, "onUpdate"); 868 869 let cleanup = stubTopSites(sandbox); 870 871 sandbox.stub(TopSites, "refresh"); 872 await TopSites.init(); 873 874 info("TopSites.init should call refresh"); 875 Assert.ok(TopSites.refresh.calledOnce, "refresh called once"); 876 Assert.ok( 877 TopSites.refresh.calledWithExactly({ 878 isStartup: true, 879 }) 880 ); 881 882 TopSites.uninit(); 883 sandbox.restore(); 884 await cleanup(); 885 }); 886 887 /** 888 * If multiple callers are attempting to initializing TopSites, we should 889 * initialize only once and wait until its completed. 890 */ 891 add_task(async function test_multiple_init() { 892 info("Initing TopSites multiple times should call _readDefaults only once."); 893 let sandbox = sinon.createSandbox(); 894 sandbox.stub(NimbusFeatures.newtab, "onUpdate"); 895 sandbox.stub(TopSites, "_readDefaults"); 896 let cleanup = stubTopSites(sandbox); 897 898 Assert.ok(TopSites._readDefaults.notCalled, "Read defaults not called."); 899 for (let i = 0; i < 5; ++i) { 900 await TopSites.init(); 901 } 902 Assert.ok(TopSites._readDefaults.calledOnce, "Read defaults called once."); 903 904 sandbox.restore(); 905 await cleanup(); 906 }); 907 908 add_task(async function test_multiple_init_delay() { 909 TopSites.uninit(); 910 911 info( 912 "Initing TopSites multiple times should allow callers " + 913 "only call readDefaults once and wait until its finished." 914 ); 915 let sandbox = sinon.createSandbox(); 916 917 let resolvePromise; 918 let promise = new Promise(resolve => { 919 resolvePromise = resolve; 920 }); 921 922 sandbox.stub(NimbusFeatures.newtab, "onUpdate"); 923 sandbox.stub(TopSites, "_readDefaults").returns(promise); 924 let cleanup = stubTopSites(sandbox); 925 926 Assert.ok(TopSites._readDefaults.notCalled, "Read defaults not called."); 927 let finishedPromiseCount = 0; 928 let promises = []; 929 let callInit = async () => { 930 await TopSites.init(); 931 ++finishedPromiseCount; 932 }; 933 for (let i = 0; i < 5; ++i) { 934 promises.push(callInit()); 935 } 936 Assert.equal( 937 finishedPromiseCount, 938 0, 939 "Finished promise count should be equal." 940 ); 941 Assert.ok(TopSites._readDefaults.calledOnce, "Read defaults called once."); 942 943 info("Resolve the promises."); 944 resolvePromise(); 945 await Promise.all(promises); 946 Assert.equal( 947 finishedPromiseCount, 948 5, 949 "Finished promise count should be equal." 950 ); 951 Assert.ok( 952 TopSites._readDefaults.calledOnce, 953 "Read defaults was still only called once." 954 ); 955 956 sandbox.restore(); 957 await cleanup(); 958 }); 959 960 add_task(async function test_uninit() { 961 info("Un-initing TopSites should expire caches."); 962 let sandbox = sinon.createSandbox(); 963 964 let cleanup = stubTopSites(sandbox); 965 sandbox.stub(TopSites, "refresh"); 966 await TopSites.init(); 967 968 sandbox.stub(TopSites.pinnedCache, "expire"); 969 sandbox.stub(TopSites.frecentCache, "expire"); 970 TopSites.uninit(); 971 972 Assert.ok( 973 TopSites.pinnedCache.expire.calledOnce, 974 "pinnedCache.expire called once" 975 ); 976 Assert.ok( 977 TopSites.frecentCache.expire.calledOnce, 978 "frecentCache.expire called once" 979 ); 980 981 sandbox.restore(); 982 await cleanup(); 983 }); 984 985 add_task(async function test_get_sites_init() { 986 info("TopSites.getSites should initialize TopSites if its not inited."); 987 let sandbox = sinon.createSandbox(); 988 989 let cleanup = stubTopSites(sandbox); 990 sandbox.stub(TopSites, "init"); 991 992 Assert.ok(TopSites.init.notCalled, "TopSites.init not called."); 993 await TopSites.getSites(); 994 Assert.ok(TopSites.init.calledOnce, "TopSites.init called once."); 995 996 sandbox.restore(); 997 await cleanup(); 998 }); 999 1000 add_task(async function test_get_sites_already_inited() { 1001 info( 1002 "TopSites.getSites should not call related initialization methods " + 1003 "more than once if TopSites is already inited." 1004 ); 1005 let sandbox = sinon.createSandbox(); 1006 1007 let cleanup = stubTopSites(sandbox); 1008 sandbox.spy(TopSites, "_readDefaults"); 1009 await TopSites.init(); 1010 1011 Assert.ok( 1012 TopSites._readDefaults.calledOnce, 1013 "TopSites._readDefaults called once." 1014 ); 1015 Assert.ok( 1016 TopSites.updateCustomSearchShortcuts.calledOnce, 1017 "TopSites.updateCustomSearchShortcuts called once." 1018 ); 1019 await TopSites.getSites(); 1020 Assert.ok( 1021 TopSites._readDefaults.calledOnce, 1022 "TopSites._readDefaults still only called once." 1023 ); 1024 Assert.ok( 1025 TopSites.updateCustomSearchShortcuts.calledOnce, 1026 "TopSites.updateCustomSearchShortcuts still only called once." 1027 ); 1028 1029 sandbox.restore(); 1030 await cleanup(); 1031 }); 1032 1033 add_task(async function test_get_sites_delayed_init() { 1034 info("TopSites.getSites should wait until initialization is done."); 1035 let sandbox = sinon.createSandbox(); 1036 1037 let cleanup = stubTopSites(sandbox); 1038 1039 // Ensure it's not initialized. 1040 TopSites.uninit(); 1041 1042 let resolvePromise; 1043 let promise = new Promise(resolve => { 1044 resolvePromise = resolve; 1045 }); 1046 sandbox.stub(TopSites, "init").returns(promise); 1047 1048 let promises = []; 1049 let finishedPromiseCount = 0; 1050 let callGetSites = async () => { 1051 await TopSites.getSites(); 1052 finishedPromiseCount += 1; 1053 }; 1054 for (let i = 0; i < 5; ++i) { 1055 promises.push(callGetSites()); 1056 } 1057 1058 Assert.equal( 1059 finishedPromiseCount, 1060 0, 1061 "All calls to TopSites.getSites() haven't finished." 1062 ); 1063 resolvePromise(); 1064 await Promise.all(promises); 1065 Assert.equal( 1066 finishedPromiseCount, 1067 5, 1068 "All calls to TopSites.getSites() finished." 1069 ); 1070 1071 sandbox.restore(); 1072 await cleanup(); 1073 }); 1074 1075 add_task(async function test_refresh() { 1076 let sandbox = sinon.createSandbox(); 1077 1078 sandbox.stub(NimbusFeatures.newtab, "onUpdate"); 1079 1080 let cleanup = stubTopSites(sandbox); 1081 1082 sandbox.stub(TopSites, "_fetchIcon"); 1083 TopSites._startedUp = true; 1084 1085 info("TopSites.refresh should wait for tippytop to initialize"); 1086 TopSites._tippyTopProvider.initialized = false; 1087 sandbox.stub(TopSites._tippyTopProvider, "init").resolves(); 1088 1089 await TopSites.refresh(); 1090 1091 Assert.ok( 1092 TopSites._tippyTopProvider.init.calledOnce, 1093 "TopSites._tippyTopProvider.init called once" 1094 ); 1095 1096 info( 1097 "TopSites.refresh should not init the tippyTopProvider if already initialized" 1098 ); 1099 TopSites._tippyTopProvider.initialized = true; 1100 TopSites._tippyTopProvider.init.resetHistory(); 1101 1102 await TopSites.refresh(); 1103 1104 Assert.ok( 1105 TopSites._tippyTopProvider.init.notCalled, 1106 "tippyTopProvider not initted again" 1107 ); 1108 1109 sandbox.restore(); 1110 await cleanup(); 1111 }); 1112 1113 add_task(async function test_refresh_updateTopSites() { 1114 // Ensure that TopSites isn't already initialized. 1115 TopSites.uninit(); 1116 1117 let sandbox = sinon.createSandbox(); 1118 let cleanup = stubTopSites(sandbox); 1119 1120 await TopSites.init(); 1121 TopSites._reset(); 1122 1123 // Clear the internal store. 1124 TopSites._reset(); 1125 1126 let sites = await TopSites.getSites(); 1127 Assert.equal(sites.length, 0, "Sites is empty."); 1128 1129 info("TopSites.refresh should update TopSites.sites"); 1130 let promise = TestUtils.topicObserved("topsites-refreshed"); 1131 // TODO: On New Tab, subscribe to updates to Top Sites. 1132 await TopSites.refresh({ isStartup: true }); 1133 await promise; 1134 1135 sites = await TopSites.getSites(); 1136 Assert.ok(sites.length, "Sites has values."); 1137 1138 sandbox.restore(); 1139 await cleanup(); 1140 }); 1141 1142 add_task(async function test_refresh_dispatch() { 1143 let sandbox = sinon.createSandbox(); 1144 1145 info("TopSites.refresh should dispatch an action with the links returned"); 1146 1147 let cleanup = stubTopSites(sandbox); 1148 sandbox.stub(TopSites, "_fetchIcon"); 1149 TopSites._startedUp = true; 1150 1151 await TopSites.refresh(); 1152 let reference = FAKE_LINKS.map(site => 1153 Object.assign({}, site, { 1154 hostname: NewTabUtils.shortURL(site), 1155 typedBonus: true, 1156 }) 1157 ); 1158 1159 // TODO: On New Tab, subscribe to updates to Top Sites. 1160 let sites = await TopSites.getSites(); 1161 Assert.deepEqual(sites, reference, "Sites are updated."); 1162 1163 sandbox.restore(); 1164 await cleanup(); 1165 }); 1166 1167 add_task(async function test_refresh_empty_slots() { 1168 let sandbox = sinon.createSandbox(); 1169 1170 info( 1171 "TopSites.refresh should handle empty slots in the resulting top sites array" 1172 ); 1173 1174 let cleanup = stubTopSites(sandbox); 1175 sandbox.stub(TopSites, "_fetchIcon"); 1176 TopSites._startedUp = true; 1177 1178 gGetTopSitesStub.resolves([FAKE_LINKS[0]]); 1179 sandbox 1180 .stub(NewTabUtils.pinnedLinks, "links") 1181 .get(() => [ 1182 null, 1183 null, 1184 FAKE_LINKS[1], 1185 null, 1186 null, 1187 null, 1188 null, 1189 null, 1190 FAKE_LINKS[2], 1191 ]); 1192 await TopSites.refresh(); 1193 1194 let reference = FAKE_LINKS.map(site => 1195 Object.assign({}, site, { 1196 hostname: NewTabUtils.shortURL(site), 1197 typedBonus: true, 1198 }) 1199 ); 1200 const expected = [ 1201 reference[0], 1202 null, 1203 createExpectedPinnedLink(reference[1], 2), 1204 null, 1205 null, 1206 null, 1207 null, 1208 null, 1209 createExpectedPinnedLink(reference[2], 8), 1210 ]; 1211 1212 // TODO: On New Tab, subscribe to updates to Top Sites. 1213 let sites = await TopSites.getSites(); 1214 assertLinks(sites, expected); 1215 1216 gGetTopSitesStub.resolves(FAKE_LINKS); 1217 sandbox.restore(); 1218 await cleanup(); 1219 }); 1220 1221 add_task(async function test_insert_part_2() { 1222 let sandbox = sinon.createSandbox(); 1223 1224 info( 1225 "TopSites.handlePlacesEvents should call refresh without a target " + 1226 "if we clear history." 1227 ); 1228 1229 let cleanup = stubTopSites(sandbox); 1230 sandbox.stub(TopSites, "refresh"); 1231 TopSites.refresh.resetHistory(); 1232 await PlacesUtils.history.clear(); 1233 Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once"); 1234 1235 TopSites.refresh.resetHistory(); 1236 1237 info( 1238 "TopSites.handlePlacesEvents should call refresh without a target " + 1239 "if we remove a Topsite from history" 1240 ); 1241 let uri = Services.io.newURI("https://www.example.com/"); 1242 await PlacesTestUtils.addVisits({ 1243 uri, 1244 transition: PlacesUtils.history.TRANSITION_TYPED, 1245 visitDate: Date.now() * 1000, 1246 }); 1247 Assert.ok(TopSites.refresh.notCalled, "TopSites.refresh not called"); 1248 await PlacesUtils.history.remove(uri); 1249 1250 Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once"); 1251 1252 info("TopSites.handlePlacesEvents should call refresh on newtab-linkBlocked"); 1253 TopSites.refresh.resetHistory(); 1254 // The event dispatched in NewTabUtils when a link is blocked; 1255 TopSites.observe(null, "newtab-linkBlocked", null); 1256 Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once"); 1257 1258 info("TopSites should call refresh on bookmark-added"); 1259 TopSites.refresh.resetHistory(); 1260 let bookmark = await PlacesUtils.bookmarks.insert({ 1261 url: "https://bookmark.example.com", 1262 title: "Bookmark 1", 1263 parentGuid: PlacesUtils.bookmarks.unfiledGuid, 1264 }); 1265 Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once"); 1266 1267 info("TopSites should call refresh on bookmark-removed"); 1268 TopSites.refresh.resetHistory(); 1269 await PlacesUtils.bookmarks.remove(bookmark); 1270 Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once"); 1271 1272 info( 1273 "TopSites.insert should call pin with correct args " + 1274 "without an index specified" 1275 ); 1276 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1277 1278 let addAction = { 1279 data: { site: { url: "foo.bar", label: "foo" } }, 1280 }; 1281 TopSites.insert(addAction); 1282 Assert.ok( 1283 NewTabUtils.pinnedLinks.pin.calledOnce, 1284 "NewTabUtils.pinnedLinks.pin called once" 1285 ); 1286 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(addAction.data.site, 0)); 1287 1288 info("TopSites.insert should call pin with correct args"); 1289 NewTabUtils.pinnedLinks.pin.resetHistory(); 1290 let dropAction = { 1291 data: { site: { url: "foo.bar", label: "foo" }, index: 3 }, 1292 }; 1293 TopSites.insert(dropAction); 1294 Assert.ok( 1295 NewTabUtils.pinnedLinks.pin.calledOnce, 1296 "NewTabUtils.pinnedLinks.pin called once" 1297 ); 1298 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(dropAction.data.site, 3)); 1299 1300 sandbox.restore(); 1301 await cleanup(); 1302 }); 1303 1304 add_task(async function test_insert_part_1() { 1305 let sandbox = sinon.createSandbox(); 1306 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1307 let cleanup = stubTopSites(sandbox); 1308 1309 { 1310 info( 1311 "TopSites.insert should pin site in first slot of pinned list with " + 1312 "empty first slot" 1313 ); 1314 1315 sandbox 1316 .stub(NewTabUtils.pinnedLinks, "links") 1317 .get(() => [null, { url: "example.com" }]); 1318 let site = { url: "foo.bar", label: "foo" }; 1319 await TopSites.insert({ data: { site } }); 1320 Assert.ok( 1321 NewTabUtils.pinnedLinks.pin.calledOnce, 1322 "NewTabUtils.pinnedLinks.pin called once" 1323 ); 1324 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1325 NewTabUtils.pinnedLinks.pin.resetHistory(); 1326 } 1327 1328 { 1329 info( 1330 "TopSites.insert should move a pinned site in first slot to the " + 1331 "next slot: part 1" 1332 ); 1333 let site1 = { url: "example.com" }; 1334 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [site1]); 1335 let site = { url: "foo.bar", label: "foo" }; 1336 1337 await TopSites.insert({ data: { site } }); 1338 Assert.ok( 1339 NewTabUtils.pinnedLinks.pin.calledTwice, 1340 "NewTabUtils.pinnedLinks.pin called twice" 1341 ); 1342 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1343 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1)); 1344 NewTabUtils.pinnedLinks.pin.resetHistory(); 1345 } 1346 1347 { 1348 info( 1349 "TopSites.insert should move a pinned site in first slot to the " + 1350 "next slot: part 2" 1351 ); 1352 let site1 = { url: "example.com" }; 1353 let site2 = { url: "example.org" }; 1354 sandbox 1355 .stub(NewTabUtils.pinnedLinks, "links") 1356 .get(() => [site1, null, site2]); 1357 let site = { url: "foo.bar", label: "foo" }; 1358 await TopSites.insert({ data: { site } }); 1359 Assert.ok( 1360 NewTabUtils.pinnedLinks.pin.calledTwice, 1361 "NewTabUtils.pinnedLinks.pin called twice" 1362 ); 1363 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1364 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1)); 1365 NewTabUtils.pinnedLinks.pin.resetHistory(); 1366 } 1367 1368 sandbox.restore(); 1369 await cleanup(); 1370 }); 1371 1372 add_task(async function test_insert_part_2() { 1373 let sandbox = sinon.createSandbox(); 1374 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1375 let cleanup = stubTopSites(sandbox); 1376 1377 { 1378 info( 1379 "TopSites.insert should unpin the last site if all slots are " + 1380 "already pinned" 1381 ); 1382 let site1 = { url: "example.com" }; 1383 let site2 = { url: "example.org" }; 1384 let site3 = { url: "example.net" }; 1385 let site4 = { url: "example.biz" }; 1386 let site5 = { url: "example.info" }; 1387 let site6 = { url: "example.news" }; 1388 let site7 = { url: "example.lol" }; 1389 let site8 = { url: "example.golf" }; 1390 sandbox 1391 .stub(NewTabUtils.pinnedLinks, "links") 1392 .get(() => [site1, site2, site3, site4, site5, site6, site7, site8]); 1393 Services.prefs.setIntPref( 1394 "browser.newtabpage.activity-stream.topSitesRows", 1395 1 1396 ); 1397 let site = { url: "foo.bar", label: "foo" }; 1398 await TopSites.insert({ data: { site } }); 1399 Assert.equal( 1400 NewTabUtils.pinnedLinks.pin.callCount, 1401 8, 1402 "NewTabUtils.pinnedLinks.pin called 8 times" 1403 ); 1404 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1405 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1)); 1406 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 2)); 1407 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site3, 3)); 1408 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site4, 4)); 1409 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site5, 5)); 1410 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site6, 6)); 1411 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site7, 7)); 1412 NewTabUtils.pinnedLinks.pin.resetHistory(); 1413 Services.prefs.clearUserPref( 1414 "browser.newtabpage.activity-stream.topSitesRows" 1415 ); 1416 } 1417 1418 { 1419 info("TopSites.insert should trigger refresh"); 1420 sandbox.stub(TopSites, "refresh"); 1421 let addAction = { 1422 data: { site: { url: "foo.com" } }, 1423 }; 1424 1425 await TopSites.insert(addAction); 1426 1427 Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once"); 1428 } 1429 1430 { 1431 info("TopSites.insert should correctly handle different index values"); 1432 let index = -1; 1433 let site = { url: "foo.bar", label: "foo" }; 1434 let action = { data: { index, site } }; 1435 1436 await TopSites.insert(action); 1437 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1438 1439 index = undefined; 1440 await TopSites.insert(action); 1441 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0)); 1442 1443 NewTabUtils.pinnedLinks.pin.resetHistory(); 1444 } 1445 1446 sandbox.restore(); 1447 await cleanup(); 1448 }); 1449 1450 add_task(async function test_insert_part_3() { 1451 let sandbox = sinon.createSandbox(); 1452 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1453 let cleanup = stubTopSites(sandbox); 1454 1455 { 1456 info("TopSites.insert should pin site in specified slot that is free"); 1457 sandbox 1458 .stub(NewTabUtils.pinnedLinks, "links") 1459 .get(() => [null, { url: "example.com" }]); 1460 1461 let site = { url: "foo.bar", label: "foo" }; 1462 1463 await TopSites.insert({ data: { index: 2, site, draggedFromIndex: 0 } }); 1464 Assert.ok( 1465 NewTabUtils.pinnedLinks.pin.calledOnce, 1466 "NewTabUtils.pinnedLinks.pin called once" 1467 ); 1468 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1469 1470 NewTabUtils.pinnedLinks.pin.resetHistory(); 1471 } 1472 1473 { 1474 info( 1475 "TopSites.insert should move a pinned site in specified slot " + 1476 "to the next slot" 1477 ); 1478 sandbox 1479 .stub(NewTabUtils.pinnedLinks, "links") 1480 .get(() => [null, null, { url: "example.com" }]); 1481 1482 let site = { url: "foo.bar", label: "foo" }; 1483 1484 await TopSites.insert({ data: { index: 2, site, draggedFromIndex: 3 } }); 1485 Assert.ok( 1486 NewTabUtils.pinnedLinks.pin.calledTwice, 1487 "NewTabUtils.pinnedLinks.pin called twice" 1488 ); 1489 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1490 Assert.ok( 1491 NewTabUtils.pinnedLinks.pin.calledWith({ url: "example.com" }, 3) 1492 ); 1493 1494 NewTabUtils.pinnedLinks.pin.resetHistory(); 1495 } 1496 1497 { 1498 info( 1499 "TopSites.insert should move pinned sites in the direction " + 1500 "of the dragged site" 1501 ); 1502 1503 let site1 = { url: "foo.bar", label: "foo" }; 1504 let site2 = { url: "example.com", label: "example" }; 1505 sandbox 1506 .stub(NewTabUtils.pinnedLinks, "links") 1507 .get(() => [null, null, site2]); 1508 1509 await TopSites.insert({ 1510 data: { index: 2, site: site1, draggedFromIndex: 0 }, 1511 }); 1512 Assert.ok( 1513 NewTabUtils.pinnedLinks.pin.calledTwice, 1514 "NewTabUtils.pinnedLinks.pin called twice" 1515 ); 1516 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 2)); 1517 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 1)); 1518 NewTabUtils.pinnedLinks.pin.resetHistory(); 1519 1520 await TopSites.insert({ 1521 data: { index: 2, site: site1, draggedFromIndex: 5 }, 1522 }); 1523 Assert.ok( 1524 NewTabUtils.pinnedLinks.pin.calledTwice, 1525 "NewTabUtils.pinnedLinks.pin called twice" 1526 ); 1527 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 2)); 1528 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 3)); 1529 NewTabUtils.pinnedLinks.pin.resetHistory(); 1530 } 1531 1532 { 1533 info("TopSites.insert should not insert past the visible top sites"); 1534 let site1 = { url: "foo.bar", label: "foo" }; 1535 await TopSites.insert({ 1536 data: { index: 42, site: site1, draggedFromIndex: 0 }, 1537 }); 1538 Assert.ok( 1539 NewTabUtils.pinnedLinks.pin.notCalled, 1540 "NewTabUtils.pinnedLinks.pin wasn't called" 1541 ); 1542 1543 NewTabUtils.pinnedLinks.pin.resetHistory(); 1544 } 1545 1546 sandbox.restore(); 1547 await cleanup(); 1548 }); 1549 1550 add_task(async function test_pin_part_1() { 1551 let sandbox = sinon.createSandbox(); 1552 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1553 sandbox.spy(TopSites.pinnedCache, "request"); 1554 let cleanup = stubTopSites(sandbox); 1555 1556 { 1557 info("TopSites.pin should pin site in specified slot empty pinned list"); 1558 let site = { 1559 url: "foo.bar", 1560 label: "foo", 1561 }; 1562 Assert.ok( 1563 TopSites.pinnedCache.request.notCalled, 1564 "TopSites.pinnedCache.request not called" 1565 ); 1566 await TopSites.pin({ data: { index: 2, site } }); 1567 Assert.ok( 1568 NewTabUtils.pinnedLinks.pin.called, 1569 "NewTabUtils.pinnedLinks.pin called" 1570 ); 1571 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1572 NewTabUtils.pinnedLinks.pin.resetHistory(); 1573 TopSites.pinnedCache.request.resetHistory(); 1574 } 1575 1576 { 1577 info( 1578 "TopSites.pin should not do a link object lookup if custom " + 1579 "screenshot field is not set" 1580 ); 1581 let site = { url: "foo.bar", label: "foo" }; 1582 await TopSites.pin({ data: { index: 2, site } }); 1583 Assert.ok( 1584 !TopSites.pinnedCache.request.called, 1585 "TopSites.pinnedCache.request never called" 1586 ); 1587 NewTabUtils.pinnedLinks.pin.resetHistory(); 1588 TopSites.pinnedCache.request.resetHistory(); 1589 } 1590 1591 { 1592 info( 1593 "TopSites.pin should pin site in specified slot of pinned " + 1594 "list that is free" 1595 ); 1596 sandbox 1597 .stub(NewTabUtils.pinnedLinks, "links") 1598 .get(() => [null, { url: "example.com" }]); 1599 1600 let site = { url: "foo.bar", label: "foo" }; 1601 await TopSites.pin({ data: { index: 2, site } }); 1602 Assert.ok( 1603 NewTabUtils.pinnedLinks.pin.calledOnce, 1604 "NewTabUtils.pinnedLinks.pin called once" 1605 ); 1606 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1607 NewTabUtils.pinnedLinks.pin.resetHistory(); 1608 } 1609 1610 sandbox.restore(); 1611 await cleanup(); 1612 }); 1613 1614 add_task(async function test_pin_part_2() { 1615 let sandbox = sinon.createSandbox(); 1616 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1617 1618 { 1619 info("TopSites.pin should save the searchTopSite attribute if set"); 1620 sandbox 1621 .stub(NewTabUtils.pinnedLinks, "links") 1622 .get(() => [null, { url: "example.com" }]); 1623 1624 let site = { url: "foo.bar", label: "foo", searchTopSite: true }; 1625 let cleanup = stubTopSites(sandbox); 1626 await TopSites.pin({ data: { index: 2, site } }); 1627 Assert.ok( 1628 NewTabUtils.pinnedLinks.pin.calledOnce, 1629 "NewTabUtils.pinnedLinks.pin called once" 1630 ); 1631 Assert.ok(NewTabUtils.pinnedLinks.pin.firstCall.args[0].searchTopSite); 1632 NewTabUtils.pinnedLinks.pin.resetHistory(); 1633 await cleanup(); 1634 } 1635 1636 { 1637 info( 1638 "TopSites.pin should NOT move a pinned site in specified " + 1639 "slot to the next slot" 1640 ); 1641 sandbox 1642 .stub(NewTabUtils.pinnedLinks, "links") 1643 .get(() => [null, null, { url: "example.com" }]); 1644 1645 let site = { url: "foo.bar", label: "foo" }; 1646 let cleanup = stubTopSites(sandbox); 1647 await TopSites.pin({ data: { index: 2, site } }); 1648 Assert.ok( 1649 NewTabUtils.pinnedLinks.pin.calledOnce, 1650 "NewTabUtils.pinnedLinks.pin called once" 1651 ); 1652 Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2)); 1653 NewTabUtils.pinnedLinks.pin.resetHistory(); 1654 await cleanup(); 1655 } 1656 1657 { 1658 info( 1659 "TopSites.pin should not update LinksCache object with screenshot data" + 1660 "properties between migrations" 1661 ); 1662 sandbox 1663 .stub(NewTabUtils.pinnedLinks, "links") 1664 .get(() => [{ url: "https://foo.com/" }]); 1665 1666 let cleanup = stubTopSites(sandbox); 1667 let pinnedLinks = await TopSites.pinnedCache.request(); 1668 Assert.equal(pinnedLinks.length, 1); 1669 TopSites.pinnedCache.expire(); 1670 1671 pinnedLinks[0].__sharedCache.updateLink("screenshot", "foo"); 1672 1673 pinnedLinks = await TopSites.pinnedCache.request(); 1674 Assert.equal(pinnedLinks[0].screenshot, undefined); 1675 1676 // Force cache expiration in order to trigger a migration of objects 1677 TopSites.pinnedCache.expire(); 1678 pinnedLinks[0].__sharedCache.updateLink("screenshot", "bar"); 1679 1680 pinnedLinks = await TopSites.pinnedCache.request(); 1681 Assert.equal(pinnedLinks[0].screenshot, undefined); 1682 await cleanup(); 1683 } 1684 1685 sandbox.restore(); 1686 }); 1687 1688 add_task(async function test_pin_part_3() { 1689 let sandbox = sinon.createSandbox(); 1690 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1691 sandbox.spy(TopSites, "insert"); 1692 1693 { 1694 info("TopSites.pin should call insert if index < 0"); 1695 let site = { url: "foo.bar", label: "foo" }; 1696 let action = { data: { index: -1, site } }; 1697 let cleanup = stubTopSites(sandbox); 1698 await TopSites.pin(action); 1699 1700 Assert.ok(TopSites.insert.calledOnce, "TopSites.insert called once"); 1701 Assert.ok(TopSites.insert.calledWithExactly(action)); 1702 NewTabUtils.pinnedLinks.pin.resetHistory(); 1703 TopSites.insert.resetHistory(); 1704 await cleanup(); 1705 } 1706 1707 { 1708 info("TopSites.pin should not call insert if index == 0"); 1709 let site = { url: "foo.bar", label: "foo" }; 1710 let action = { data: { index: 0, site } }; 1711 let cleanup = stubTopSites(sandbox); 1712 await TopSites.pin(action); 1713 1714 Assert.ok(!TopSites.insert.called, "TopSites.insert not called"); 1715 NewTabUtils.pinnedLinks.pin.resetHistory(); 1716 await cleanup(); 1717 } 1718 1719 { 1720 info("TopSites.pin should trigger refresh"); 1721 let cleanup = stubTopSites(sandbox); 1722 sandbox.stub(TopSites, "refresh"); 1723 let pinExistingAction = { 1724 data: { site: FAKE_LINKS[4], index: 4 }, 1725 }; 1726 1727 await TopSites.pin(pinExistingAction); 1728 1729 Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once"); 1730 NewTabUtils.pinnedLinks.pin.resetHistory(); 1731 await cleanup(); 1732 } 1733 1734 sandbox.restore(); 1735 }); 1736 1737 add_task(async function test_pin_part_4() { 1738 let sandbox = sinon.createSandbox(); 1739 let cleanup = stubTopSites(sandbox); 1740 1741 info("TopSites.pin should call with correct parameters"); 1742 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 1743 sandbox.spy(TopSites, "pin"); 1744 1745 let pinAction = { 1746 data: { site: { url: "foo.com" }, index: 7 }, 1747 }; 1748 await TopSites.pin(pinAction); 1749 Assert.ok( 1750 NewTabUtils.pinnedLinks.pin.calledOnce, 1751 "NewTabUtils.pinnedLinks.pin called once" 1752 ); 1753 Assert.ok( 1754 NewTabUtils.pinnedLinks.pin.calledWithExactly( 1755 pinAction.data.site, 1756 pinAction.data.index 1757 ) 1758 ); 1759 Assert.ok( 1760 TopSites.pin.calledOnce, 1761 "TopSites.pin should have been called once" 1762 ); 1763 1764 info( 1765 "TopSites.pin should unblock a previously blocked top site if " + 1766 "we are now adding it manually via 'Add a Top Site' option" 1767 ); 1768 sandbox.stub(NewTabUtils.blockedLinks, "unblock"); 1769 pinAction = { 1770 data: { site: { url: "foo.com" }, index: -1 }, 1771 }; 1772 await TopSites.pin(pinAction); 1773 Assert.ok( 1774 NewTabUtils.blockedLinks.unblock.calledWith({ 1775 url: pinAction.data.site.url, 1776 }) 1777 ); 1778 1779 info("TopSites.pin should call insert"); 1780 sandbox.stub(TopSites, "insert"); 1781 let addAction = { 1782 data: { site: { url: "foo.com" } }, 1783 }; 1784 1785 await TopSites.pin(addAction); 1786 Assert.ok(TopSites.insert.calledOnce, "TopSites.insert called once"); 1787 1788 info("TopSites.unpin should call unpin with correct parameters"); 1789 1790 sandbox 1791 .stub(NewTabUtils.pinnedLinks, "links") 1792 .get(() => [ 1793 null, 1794 null, 1795 { url: "foo.com" }, 1796 null, 1797 null, 1798 null, 1799 null, 1800 null, 1801 FAKE_LINKS[0], 1802 ]); 1803 sandbox.stub(NewTabUtils.pinnedLinks, "unpin"); 1804 1805 let unpinAction = { 1806 data: { site: { url: "foo.com" } }, 1807 }; 1808 await TopSites.unpin(unpinAction); 1809 Assert.ok( 1810 NewTabUtils.pinnedLinks.unpin.calledOnce, 1811 "NewTabUtils.pinnedLinks.unpin called once" 1812 ); 1813 Assert.ok(NewTabUtils.pinnedLinks.unpin.calledWith(unpinAction.data.site)); 1814 1815 sandbox.restore(); 1816 await cleanup(); 1817 }); 1818 1819 add_task(async function test_integration() { 1820 let sandbox = sinon.createSandbox(); 1821 1822 info("Test adding a pinned site and removing it with actions"); 1823 let cleanup = stubTopSites(sandbox); 1824 1825 TopSites._startedUp = true; 1826 1827 TopSites._requestRichIcon = sandbox.stub(); 1828 let url = "https://pin.me"; 1829 sandbox.stub(NewTabUtils.pinnedLinks, "pin").callsFake(link => { 1830 NewTabUtils.pinnedLinks.links.push(link); 1831 }); 1832 1833 await TopSites.insert({ data: { site: { url } } }); 1834 await TestUtils.topicObserved("topsites-refreshed"); 1835 let oldSites = await TopSites.getSites(); 1836 NewTabUtils.pinnedLinks.links.pop(); 1837 // The event dispatched in NewTabUtils when a link is blocked; 1838 TopSites.observe(null, "newtab-linkBlocked", null); 1839 await TestUtils.topicObserved("topsites-refreshed"); 1840 let newSites = await TopSites.getSites(); 1841 1842 Assert.equal(oldSites[0].url, url, "Url matches."); 1843 Assert.equal(newSites[0].url, FAKE_LINKS[0].url, "Url matches."); 1844 1845 sandbox.restore(); 1846 await cleanup(); 1847 }); 1848 1849 add_task(async function test_improvesearch_noDefaultSearchTile_experiment() { 1850 let sandbox = sinon.createSandbox(); 1851 1852 sandbox.stub(SearchService.prototype, "getDefault").resolves({ 1853 identifier: "google", 1854 }); 1855 1856 { 1857 info( 1858 "TopSites.getLinksWithDefaults should filter out alexa top 5 " + 1859 "search from the default sites" 1860 ); 1861 let cleanup = stubTopSites(sandbox); 1862 Services.prefs.setBoolPref( 1863 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 1864 true 1865 ); 1866 let top5Test = [ 1867 "https://google.com", 1868 "https://search.yahoo.com", 1869 "https://yahoo.com", 1870 "https://bing.com", 1871 "https://ask.com", 1872 "https://duckduckgo.com", 1873 ]; 1874 1875 gGetTopSitesStub.resolves([ 1876 { url: "https://amazon.com" }, 1877 ...top5Test.map(url => ({ url })), 1878 ]); 1879 1880 const urlsReturned = (await TopSites.getLinksWithDefaults()).map( 1881 link => link.url 1882 ); 1883 Assert.ok( 1884 urlsReturned.includes("https://amazon.com"), 1885 "amazon included in default links" 1886 ); 1887 top5Test.forEach(url => 1888 Assert.ok(!urlsReturned.includes(url), `Should not include ${url}`) 1889 ); 1890 1891 Services.prefs.clearUserPref( 1892 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile" 1893 ); 1894 gGetTopSitesStub.resolves(FAKE_LINKS); 1895 await cleanup(); 1896 } 1897 1898 { 1899 info( 1900 "TopSites.getLinksWithDefaults should not filter out alexa, default " + 1901 "search from the query results if the experiment pref is off" 1902 ); 1903 let cleanup = stubTopSites(sandbox); 1904 Services.prefs.setBoolPref( 1905 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 1906 false 1907 ); 1908 gGetTopSitesStub.resolves([ 1909 { url: "https://google.com" }, 1910 { url: "https://foo.com" }, 1911 { url: "https://duckduckgo" }, 1912 ]); 1913 let urlsReturned = (await TopSites.getLinksWithDefaults()).map( 1914 link => link.url 1915 ); 1916 1917 Assert.ok(urlsReturned.includes("https://google.com")); 1918 gGetTopSitesStub.resolves(FAKE_LINKS); 1919 Services.prefs.clearUserPref( 1920 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile" 1921 ); 1922 await cleanup(); 1923 } 1924 1925 { 1926 info( 1927 "TopSites.getLinksWithDefaults should filter out the current " + 1928 "default search from the default sites" 1929 ); 1930 let cleanup = stubTopSites(sandbox); 1931 Services.prefs.setBoolPref( 1932 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 1933 true 1934 ); 1935 1936 sandbox.stub(TopSites, "_currentSearchHostname").get(() => "amazon"); 1937 Services.prefs.setStringPref( 1938 "browser.newtabpage.activity-stream.default.sites", 1939 "https://google.com,https://amazon.com" 1940 ); 1941 gGetTopSitesStub.resolves([{ url: "https://foo.com" }]); 1942 1943 let urlsReturned = (await TopSites.getLinksWithDefaults()).map( 1944 link => link.url 1945 ); 1946 Assert.ok(!urlsReturned.includes("https://amazon.com")); 1947 1948 Services.prefs.clearUserPref( 1949 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile" 1950 ); 1951 Services.prefs.clearUserPref( 1952 "browser.newtabpage.activity-stream.default.sites" 1953 ); 1954 gGetTopSitesStub.resolves(FAKE_LINKS); 1955 await cleanup(); 1956 } 1957 1958 { 1959 info( 1960 "TopSites.getLinksWithDefaults should not filter out current " + 1961 "default search from pinned sites even if it matches the current " + 1962 "default search" 1963 ); 1964 let cleanup = stubTopSites(sandbox); 1965 Services.prefs.setBoolPref( 1966 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 1967 true 1968 ); 1969 1970 sandbox 1971 .stub(NewTabUtils.pinnedLinks, "links") 1972 .get(() => [{ url: "google.com" }]); 1973 gGetTopSitesStub.resolves([{ url: "https://foo.com" }]); 1974 1975 let urlsReturned = (await TopSites.getLinksWithDefaults()).map( 1976 link => link.url 1977 ); 1978 Assert.ok(urlsReturned.includes("google.com")); 1979 1980 Services.prefs.clearUserPref( 1981 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile" 1982 ); 1983 gGetTopSitesStub.resolves(FAKE_LINKS); 1984 await cleanup(); 1985 } 1986 1987 sandbox.restore(); 1988 }); 1989 1990 add_task( 1991 async function test_improvesearch_noDefaultSearchTile_experiment_part_2() { 1992 let sandbox = sinon.createSandbox(); 1993 1994 sandbox.stub(SearchService.prototype, "getDefault").resolves({ 1995 identifier: "google", 1996 }); 1997 1998 sandbox.stub(TopSites, "refresh"); 1999 2000 { 2001 info( 2002 "TopSites.getLinksWithDefaults should call refresh and set " + 2003 "._currentSearchHostname to the new engine hostname when the " + 2004 "default search engine has been set" 2005 ); 2006 let cleanup = stubTopSites(sandbox); 2007 Services.prefs.setBoolPref( 2008 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 2009 true 2010 ); 2011 2012 TopSites.observe( 2013 null, 2014 "browser-search-engine-modified", 2015 "engine-default" 2016 ); 2017 Assert.equal(TopSites._currentSearchHostname, "duckduckgo"); 2018 // Refresh is called twice: 2019 // 1) For the change of `noDefaultSearchTile` 2020 // 2) Default search engine changed "browser-search-engine-modified" 2021 Assert.ok(TopSites.refresh.calledTwice, "TopSites.refresh called twice"); 2022 2023 Services.prefs.clearUserPref( 2024 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile" 2025 ); 2026 gGetTopSitesStub.resolves(FAKE_LINKS); 2027 TopSites.refresh.resetHistory(); 2028 await cleanup(); 2029 } 2030 2031 { 2032 info( 2033 "TopSites.getLinksWithDefaults should call refresh when the " + 2034 "experiment pref has changed" 2035 ); 2036 let cleanup = stubTopSites(sandbox); 2037 Services.prefs.setBoolPref( 2038 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 2039 true 2040 ); 2041 Assert.ok( 2042 TopSites.refresh.calledOnce, 2043 "TopSites.refresh was called once" 2044 ); 2045 2046 Services.prefs.setBoolPref( 2047 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 2048 false 2049 ); 2050 Assert.ok( 2051 TopSites.refresh.calledTwice, 2052 "TopSites.refresh was called twice" 2053 ); 2054 2055 Services.prefs.clearUserPref( 2056 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile" 2057 ); 2058 gGetTopSitesStub.resolves(FAKE_LINKS); 2059 TopSites.refresh.resetHistory(); 2060 await cleanup(); 2061 } 2062 2063 sandbox.restore(); 2064 } 2065 ); 2066 2067 // eslint-disable-next-line max-statements 2068 add_task(async function test_improvesearch_topSitesSearchShortcuts() { 2069 let sandbox = sinon.createSandbox(); 2070 let searchEngines = [{ aliases: ["@google"] }, { aliases: ["@amazon"] }]; 2071 sandbox 2072 .stub(SearchService.prototype, "getAppProvidedEngines") 2073 .resolves(searchEngines); 2074 sandbox.stub(NewTabUtils.pinnedLinks, "pin").callsFake((site, index) => { 2075 NewTabUtils.pinnedLinks.links[index] = site; 2076 }); 2077 2078 let prepTopSites = () => { 2079 Services.prefs.setBoolPref( 2080 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", 2081 true 2082 ); 2083 Services.prefs.setStringPref( 2084 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.searchEngines", 2085 "google,amazon" 2086 ); 2087 Services.prefs.setStringPref( 2088 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.havePinned", 2089 "" 2090 ); 2091 }; 2092 2093 { 2094 info( 2095 "TopSites should updateCustomSearchShortcuts when experiment " + 2096 "pref is turned on" 2097 ); 2098 let cleanup = stubTopSites(sandbox); 2099 prepTopSites(); 2100 Services.prefs.setBoolPref( 2101 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", 2102 false 2103 ); 2104 // stubTopSites stubs updateCustomSearchShortcuts, when we need to add 2105 // a spy. 2106 TopSites.updateCustomSearchShortcuts.restore(); 2107 sandbox.spy(TopSites, "updateCustomSearchShortcuts"); 2108 2109 // turn the experiment on 2110 Services.prefs.setBoolPref( 2111 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", 2112 true 2113 ); 2114 2115 Assert.ok( 2116 TopSites.updateCustomSearchShortcuts.calledOnce, 2117 "TopSites.updateCustomSearchShortcuts called once" 2118 ); 2119 Services.prefs.clearUserPref( 2120 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts" 2121 ); 2122 TopSites.updateCustomSearchShortcuts.restore(); 2123 await cleanup(); 2124 } 2125 2126 { 2127 info( 2128 "TopSites should filter out default top sites that match a " + 2129 "hostname of a search shortcut if previously blocked" 2130 ); 2131 let cleanup = stubTopSites(sandbox); 2132 prepTopSites(); 2133 TopSites.refreshDefaults("https://amazon.ca"); 2134 sandbox 2135 .stub(NewTabUtils.blockedLinks, "links") 2136 .value([{ url: "https://amazon.com" }]); 2137 sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => { 2138 return NewTabUtils.blockedLinks.links[0].url === site.url; 2139 }); 2140 2141 let urlsReturned = (await TopSites.getLinksWithDefaults()).map( 2142 link => link.url 2143 ); 2144 Assert.ok(!urlsReturned.includes("https://amazon.ca")); 2145 await cleanup(); 2146 } 2147 2148 { 2149 info("TopSites should update frecent search topsite icon"); 2150 let cleanup = stubTopSites(sandbox); 2151 prepTopSites(); 2152 sandbox.stub(TopSites._tippyTopProvider, "processSite").callsFake(site => { 2153 site.tippyTopIcon = "icon.png"; 2154 site.backgroundColor = "#fff"; 2155 return site; 2156 }); 2157 gGetTopSitesStub.resolves([{ url: "https://google.com" }]); 2158 2159 let urlsReturned = await TopSites.getLinksWithDefaults(); 2160 2161 let defaultSearchTopsite = urlsReturned.find( 2162 s => s.url === "https://google.com" 2163 ); 2164 Assert.ok(defaultSearchTopsite.searchTopSite); 2165 Assert.equal(defaultSearchTopsite.tippyTopIcon, "icon.png"); 2166 Assert.equal(defaultSearchTopsite.backgroundColor, "#fff"); 2167 gGetTopSitesStub.resolves(FAKE_LINKS); 2168 TopSites._tippyTopProvider.processSite.restore(); 2169 await cleanup(); 2170 } 2171 2172 { 2173 info("TopSites should update default search topsite icon"); 2174 let cleanup = stubTopSites(sandbox); 2175 prepTopSites(); 2176 sandbox.stub(TopSites._tippyTopProvider, "processSite").callsFake(site => { 2177 site.tippyTopIcon = "icon.png"; 2178 site.backgroundColor = "#fff"; 2179 return site; 2180 }); 2181 gGetTopSitesStub.resolves([{ url: "https://foo.com" }]); 2182 2183 let urlsReturned = await TopSites.getLinksWithDefaults(); 2184 2185 let defaultSearchTopsite = urlsReturned.find( 2186 s => s.url === "https://amazon.com" 2187 ); 2188 Assert.ok(defaultSearchTopsite.searchTopSite); 2189 Assert.equal(defaultSearchTopsite.tippyTopIcon, "icon.png"); 2190 Assert.equal(defaultSearchTopsite.backgroundColor, "#fff"); 2191 gGetTopSitesStub.resolves(FAKE_LINKS); 2192 TopSites._tippyTopProvider.processSite.restore(); 2193 await cleanup(); 2194 } 2195 2196 { 2197 info( 2198 "TopSites should dispatch UPDATE_SEARCH_SHORTCUTS on " + 2199 "updateCustomSearchShortcuts" 2200 ); 2201 let cleanup = stubTopSites(sandbox); 2202 prepTopSites(); 2203 Services.prefs.setBoolPref( 2204 "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile", 2205 true 2206 ); 2207 // stubTopSites stubs updateCustomSearchShortcuts, when in this case, we 2208 // want to check the effect of the method. 2209 TopSites.updateCustomSearchShortcuts.restore(); 2210 await TopSites.updateCustomSearchShortcuts(); 2211 let searchShortcuts = await TopSites.getSearchShortcuts(); 2212 Assert.deepEqual(searchShortcuts, [ 2213 { 2214 keyword: "@google", 2215 shortURL: "google", 2216 url: "https://google.com", 2217 backgroundColor: undefined, 2218 smallFavicon: 2219 "chrome://activity-stream/content/data/content/tippytop/favicons/google-com.ico", 2220 tippyTopIcon: 2221 "chrome://activity-stream/content/data/content/tippytop/images/google-com@2x.png", 2222 }, 2223 { 2224 keyword: "@amazon", 2225 shortURL: "amazon", 2226 url: "https://amazon.com", 2227 backgroundColor: undefined, 2228 smallFavicon: 2229 "chrome://activity-stream/content/data/content/tippytop/favicons/amazon.ico", 2230 tippyTopIcon: 2231 "chrome://activity-stream/content/data/content/tippytop/images/amazon@2x.png", 2232 }, 2233 ]); 2234 await cleanup(); 2235 } 2236 2237 { 2238 info( 2239 "TopSites should refresh when top sites search shortcut feature gate pref changes." 2240 ); 2241 let cleanup = stubTopSites(sandbox); 2242 prepTopSites(); 2243 2244 let promise = TestUtils.topicObserved("topsites-refreshed"); 2245 Services.prefs.setBoolPref( 2246 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", 2247 false 2248 ); 2249 await promise; 2250 2251 let sites = await TopSites.getSites(); 2252 let searchTopSiteCount = sites.reduce( 2253 (acc, current) => (current.searchTopSite ? 1 : 0 + acc), 2254 0 2255 ); 2256 Assert.equal(searchTopSiteCount, 0, "Number of search top sites."); 2257 2258 promise = TestUtils.topicObserved("topsites-refreshed"); 2259 Services.prefs.setBoolPref( 2260 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts", 2261 true 2262 ); 2263 await promise; 2264 sites = await TopSites.getSites(); 2265 searchTopSiteCount = sites.reduce( 2266 (acc, current) => acc + (current.searchTopSite ? 1 : 0), 2267 0 2268 ); 2269 Assert.equal(searchTopSiteCount, 2, "Number of search top sites."); 2270 await cleanup(); 2271 } 2272 2273 Services.prefs.clearUserPref( 2274 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts" 2275 ); 2276 Services.prefs.clearUserPref( 2277 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.searchEngines" 2278 ); 2279 Services.prefs.clearUserPref( 2280 "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.havePinned" 2281 ); 2282 sandbox.restore(); 2283 }); 2284 2285 add_task(async function test_updatePinnedSearchShortcuts() { 2286 let sandbox = sinon.createSandbox(); 2287 sandbox.stub(NewTabUtils.pinnedLinks, "pin"); 2288 sandbox.stub(NewTabUtils.pinnedLinks, "unpin"); 2289 2290 { 2291 info( 2292 "TopSites.updatePinnedSearchShortcuts should unpin a " + 2293 "shortcut in deletedShortcuts" 2294 ); 2295 let cleanup = stubTopSites(sandbox); 2296 2297 let deletedShortcuts = [ 2298 { 2299 url: "https://google.com", 2300 searchVendor: "google", 2301 label: "google", 2302 searchTopSite: true, 2303 }, 2304 ]; 2305 let addedShortcuts = []; 2306 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [ 2307 null, 2308 null, 2309 { 2310 url: "https://amazon.com", 2311 searchVendor: "amazon", 2312 label: "amazon", 2313 searchTopSite: true, 2314 }, 2315 ]); 2316 TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2317 Assert.ok( 2318 NewTabUtils.pinnedLinks.pin.notCalled, 2319 "NewTabUtils.pinnedLinks.pin not called" 2320 ); 2321 Assert.ok( 2322 NewTabUtils.pinnedLinks.unpin.calledOnce, 2323 "NewTabUtils.pinnedLinks.unpin called once" 2324 ); 2325 Assert.ok( 2326 NewTabUtils.pinnedLinks.unpin.calledWith({ 2327 url: "https://google.com", 2328 }) 2329 ); 2330 2331 NewTabUtils.pinnedLinks.pin.resetHistory(); 2332 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2333 await cleanup(); 2334 } 2335 2336 { 2337 info( 2338 "TopSites.updatePinnedSearchShortcuts should pin a shortcut " + 2339 "in addedShortcuts" 2340 ); 2341 let cleanup = stubTopSites(sandbox); 2342 2343 let addedShortcuts = [ 2344 { 2345 url: "https://google.com", 2346 searchVendor: "google", 2347 label: "google", 2348 searchTopSite: true, 2349 }, 2350 ]; 2351 let deletedShortcuts = []; 2352 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [ 2353 null, 2354 null, 2355 { 2356 url: "https://amazon.com", 2357 searchVendor: "amazon", 2358 label: "amazon", 2359 searchTopSite: true, 2360 }, 2361 ]); 2362 TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2363 Assert.ok( 2364 NewTabUtils.pinnedLinks.unpin.notCalled, 2365 "NewTabUtils.pinnedLinks.unpin not called" 2366 ); 2367 Assert.ok( 2368 NewTabUtils.pinnedLinks.pin.calledOnce, 2369 "NewTabUtils.pinnedLinks.pin called once" 2370 ); 2371 Assert.ok( 2372 NewTabUtils.pinnedLinks.pin.calledWith( 2373 { 2374 label: "google", 2375 searchTopSite: true, 2376 searchVendor: "google", 2377 url: "https://google.com", 2378 }, 2379 0 2380 ) 2381 ); 2382 2383 NewTabUtils.pinnedLinks.pin.resetHistory(); 2384 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2385 await cleanup(); 2386 } 2387 2388 { 2389 info( 2390 "TopSites.updatePinnedSearchShortcuts should pin and unpin " + 2391 "in the same action" 2392 ); 2393 let cleanup = stubTopSites(sandbox); 2394 2395 let addedShortcuts = [ 2396 { 2397 url: "https://google.com", 2398 searchVendor: "google", 2399 label: "google", 2400 searchTopSite: true, 2401 }, 2402 { 2403 url: "https://ebay.com", 2404 searchVendor: "ebay", 2405 label: "ebay", 2406 searchTopSite: true, 2407 }, 2408 ]; 2409 let deletedShortcuts = [ 2410 { 2411 url: "https://amazon.com", 2412 searchVendor: "amazon", 2413 label: "amazon", 2414 searchTopSite: true, 2415 }, 2416 ]; 2417 2418 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [ 2419 { url: "https://foo.com" }, 2420 { 2421 url: "https://amazon.com", 2422 searchVendor: "amazon", 2423 label: "amazon", 2424 searchTopSite: true, 2425 }, 2426 ]); 2427 TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2428 2429 Assert.ok( 2430 NewTabUtils.pinnedLinks.unpin.calledOnce, 2431 "NewTabUtils.pinnedLinks.unpin called once" 2432 ); 2433 Assert.ok( 2434 NewTabUtils.pinnedLinks.pin.calledTwice, 2435 "NewTabUtils.pinnedLinks.pin called twice" 2436 ); 2437 2438 NewTabUtils.pinnedLinks.pin.resetHistory(); 2439 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2440 await cleanup(); 2441 } 2442 2443 { 2444 info( 2445 "TopSites.updatePinnedSearchShortcuts should pin a shortcut in " + 2446 "addedShortcuts even if pinnedLinks is full" 2447 ); 2448 let cleanup = stubTopSites(sandbox); 2449 2450 let addedShortcuts = [ 2451 { 2452 url: "https://google.com", 2453 searchVendor: "google", 2454 label: "google", 2455 searchTopSite: true, 2456 }, 2457 ]; 2458 let deletedShortcuts = []; 2459 sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => FAKE_LINKS); 2460 TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts }); 2461 2462 Assert.ok( 2463 NewTabUtils.pinnedLinks.unpin.notCalled, 2464 "NewTabUtils.pinnedLinks.unpin not called" 2465 ); 2466 Assert.ok( 2467 NewTabUtils.pinnedLinks.pin.calledWith( 2468 { label: "google", searchTopSite: true, url: "https://google.com" }, 2469 0 2470 ), 2471 "NewTabUtils.pinnedLinks.unpin not called" 2472 ); 2473 2474 NewTabUtils.pinnedLinks.pin.resetHistory(); 2475 NewTabUtils.pinnedLinks.unpin.resetHistory(); 2476 await cleanup(); 2477 } 2478 2479 sandbox.restore(); 2480 }); 2481 2482 add_task(async function test_insertPinned() { 2483 info("#insertPinned"); 2484 2485 function createLinks(count) { 2486 return new Array(count).fill(null).map((v, i) => ({ url: `site${i}.com` })); 2487 } 2488 2489 info("should place pinned links where they belong"); 2490 { 2491 let links = createLinks(12); 2492 const pinned = [ 2493 { url: "http://github.com/mozilla/activity-stream", title: "moz/a-s" }, 2494 { url: "http://example.com", title: "example" }, 2495 ]; 2496 2497 const result = insertPinned(links, pinned); 2498 for (let index of [0, 1]) { 2499 Assert.equal(result[index].url, pinned[index].url, "Pinned URL matches"); 2500 Assert.ok(result[index].isPinned, "Link is marked as pinned"); 2501 Assert.equal(result[index].pinIndex, index, "Pin index is correct"); 2502 } 2503 Assert.deepEqual(result.slice(2), links, "Remaining links are unchanged"); 2504 } 2505 2506 info("should handle empty slots in the pinned list"); 2507 { 2508 let links = createLinks(12); 2509 const pinned = [ 2510 null, 2511 { url: "http://github.com/mozilla/activity-stream", title: "moz/a-s" }, 2512 null, 2513 null, 2514 { url: "http://example.com", title: "example" }, 2515 ]; 2516 2517 const result = insertPinned(links, pinned); 2518 for (let index of [1, 4]) { 2519 Assert.equal(result[index].url, pinned[index].url, "Pinned URL matches"); 2520 Assert.ok(result[index].isPinned, "Link is marked as pinned"); 2521 Assert.equal(result[index].pinIndex, index, "Pin index is correct"); 2522 } 2523 result.splice(4, 1); 2524 result.splice(1, 1); 2525 Assert.deepEqual(result, links, "Remaining links are unchanged"); 2526 } 2527 2528 info("should handle a pinned site past the end of the list of links"); 2529 { 2530 const pinned = []; 2531 pinned[11] = { 2532 url: "http://github.com/mozilla/activity-stream", 2533 title: "moz/a-s", 2534 }; 2535 2536 const result = insertPinned([], pinned); 2537 Assert.equal(result[11].url, pinned[11].url, "Pinned URL matches"); 2538 Assert.ok(result[11].isPinned, "Link is marked as pinned"); 2539 Assert.equal(result[11].pinIndex, 11, "Pin index is correct"); 2540 } 2541 2542 info("should unpin previously pinned links no longer in the pinned list"); 2543 { 2544 let links = createLinks(12); 2545 const pinned = []; 2546 links[2].isPinned = true; 2547 links[2].pinIndex = 2; 2548 2549 const result = insertPinned(links, pinned); 2550 Assert.ok(!result[2].isPinned, "isPinned property removed"); 2551 Assert.ok(!result[2].pinIndex, "pinIndex property removed"); 2552 } 2553 2554 info("should handle a link present in both the links and pinned list"); 2555 { 2556 let links = createLinks(12); 2557 const pinned = [links[7]]; 2558 2559 const result = insertPinned(links, pinned); 2560 Assert.equal(links.length, result.length, "Length of links is unchanged"); 2561 } 2562 2563 info("should not modify the original data"); 2564 { 2565 let links = createLinks(12); 2566 const pinned = [{ url: "http://example.com" }]; 2567 2568 insertPinned(links, pinned); 2569 2570 Assert.equal( 2571 typeof pinned[0].isPinned, 2572 "undefined", 2573 "Pinned data is not mutated" 2574 ); 2575 } 2576 });