Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/browser/components/topsites/test/unit/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 73 kB image not shown  

Quelle  test_top_sites.js   Sprache: JAVA

 
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */


"use strict";

const { TopSites, insertPinned, DEFAULT_TOP_SITES } =
  ChromeUtils.importESModule("resource:///modules/topsites/TopSites.sys.mjs");

const { actionTypes: at } = ChromeUtils.importESModule(
  "resource://activity-stream/common/Actions.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  FilterAdult: "resource:///modules/FilterAdult.sys.mjs",
  NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  sinon: "resource://testing-common/Sinon.sys.mjs",
  PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  Screenshots: "resource://activity-stream/lib/Screenshots.sys.mjs",
  SearchService: "resource://gre/modules/SearchService.sys.mjs",
  TestUtils: "resource://testing-common/TestUtils.sys.mjs",
  TOP_SITES_DEFAULT_ROWS: "resource:///modules/topsites/constants.mjs",
  TOP_SITES_MAX_SITES_PER_ROW: "resource:///modules/topsites/constants.mjs",
});

const FAKE_FAVICON = "data987";
const FAKE_FAVICON_SIZE = 128;
const FAKE_FRECENCY = 200;
const FAKE_LINKS = new Array(2 * TOP_SITES_MAX_SITES_PER_ROW)
  .fill(null)
  .map((v, i) => ({
    frecency: FAKE_FRECENCY,
    url: `http://www.site${i}.com`,
  }));
const FAKE_SCREENSHOT = "data123";

function FakeTippyTopProvider() {}
FakeTippyTopProvider.prototype = {
  async init() {
    this.initialized = true;
  },
  processSite(site) {
    return site;
  },
};

let gSearchServiceInitStub;
let gGetTopSitesStub;

function stubTopSites(sandbox) {
  async function cleanup() {
    if (TopSites._refreshing) {
      info("Wait for refresh to finish.");
      // Wait for refresh to finish or else removing the store while a process
      // is running will result in errors.
      await TestUtils.topicObserved("topsites-refreshed");
      info("Top sites was refreshed.");
    }
    TopSites._tippyTopProvider.initialized = false;
    TopSites.pinnedCache.clear();
    TopSites.frecentCache.clear();
    TopSites._reset();
    stub.restore();
    info("Finished cleaning up TopSites.");
  }

  // To avoid having to setup search for each test, we stub this method and
  // unstub it when the unit test calls for the search shortcuts.
  let stub = sandbox.stub(TopSites, "updateCustomSearchShortcuts");

  TopSites._requestRichIcon = sandbox.stub();
  // Set preferences to match the store state.
  Services.prefs.setIntPref(
    "browser.newtabpage.activity-stream.topSitesRows",
    2
  );
  info("Created mock store for TopSites.");
  return cleanup;
}

function createExpectedPinnedLink(link, index) {
  link.isDefault = false;
  link.isPinned = true;
  link.searchTopSite = false;
  link.favicon = FAKE_FAVICON;
  link.faviconSize = FAKE_FAVICON_SIZE;
  link.pinIndex = index;
  return link;
}

function assertLinks(actualLinks, expectedLinks) {
  Assert.equal(
    actualLinks.length,
    expectedLinks.length,
    "Links have equal length."
  );
  for (let i = 0; i < actualLinks.length; ++i) {
    Assert.deepEqual(actualLinks[i], expectedLinks[i], "Link entry matches");
  }
}

add_setup(async () => {
  // Places requires a profile.
  do_get_profile();

  let sandbox = sinon.createSandbox();
  sandbox.stub(SearchService.prototype, "defaultEngine").get(() => {
    return { identifier: "ddg", searchUrlDomain: "duckduckgo.com" };
  });

  gGetTopSitesStub = sandbox
    .stub(NewTabUtils.activityStreamLinks, "getTopSites")
    .resolves(FAKE_LINKS);

  gSearchServiceInitStub = sandbox
    .stub(SearchService.prototype, "init")
    .resolves();

  sandbox.stub(NewTabUtils.activityStreamProvider, "_faviconBytesToDataURI");

  sandbox
    .stub(NewTabUtils.activityStreamProvider, "_addFavicons")
    .callsFake(l => {
      return Promise.resolve(
        l.map(link => {
          link.favicon = FAKE_FAVICON;
          link.faviconSize = FAKE_FAVICON_SIZE;
          return link;
        })
      );
    });

  sandbox.stub(Screenshots, "getScreenshotForURL").resolves(FAKE_SCREENSHOT);
  sandbox.spy(Screenshots, "maybeCacheScreenshot");
  sandbox.stub(Screenshots, "_shouldGetScreenshots").returns(true);

  registerCleanupFunction(() => {
    sandbox.restore();
  });
});

add_task(async function test_construction() {
  Assert.ok(TopSites._currentSearchHostname, "_currentSearchHostname defined");
});

add_task(async function test_refreshDefaults() {
  let sandbox = sinon.createSandbox();
  let cleanup = stubTopSites(sandbox);
  Assert.ok(
    !DEFAULT_TOP_SITES.length,
    "Should have 0 DEFAULT_TOP_SITES initially."
  );

  // We have to init to subscribe to changes to the preferences.
  await TopSites.init();

  info(
    "TopSites.refreshDefaults should add defaults on default.sites pref change."
  );
  Services.prefs.setStringPref(
    "browser.newtabpage.activity-stream.default.sites",
    "https://foo.com"
  );

  Assert.equal(
    DEFAULT_TOP_SITES.length,
    1,
    "Should have 1 DEFAULT_TOP_SITES now."
  );

  // Reset the DEFAULT_TOP_SITES;
  DEFAULT_TOP_SITES.length = 0;

  info("refreshDefaults should refresh on topSiteRows PREF_CHANGED");
  let refreshStub = sandbox.stub(TopSites, "refresh");
  Services.prefs.setIntPref(
    "browser.newtabpage.activity-stream.topSitesRows",
    1
  );
  Assert.ok(TopSites.refresh.calledOnce, "refresh called");
  refreshStub.restore();

  // Reset the DEFAULT_TOP_SITES;
  DEFAULT_TOP_SITES.length = 0;

  info("refreshDefaults should have default sites with .isDefault = true");
  TopSites.refreshDefaults("https://foo.com");
  Assert.equal(
    DEFAULT_TOP_SITES.length,
    1,
    "Should have a DEFAULT_TOP_SITES now."
  );
  Assert.ok(
    DEFAULT_TOP_SITES[0].isDefault,
    "Lone top site should be the default."
  );

  // Reset the DEFAULT_TOP_SITES;
  DEFAULT_TOP_SITES.length = 0;

  info("refreshDefaults should have default sites with appropriate hostname");
  TopSites.refreshDefaults("https://foo.com");
  Assert.equal(
    DEFAULT_TOP_SITES.length,
    1,
    "Should have a DEFAULT_TOP_SITES now."
  );
  let [site] = DEFAULT_TOP_SITES;
  Assert.equal(
    site.hostname,
    NewTabUtils.shortURL(site),
    "Lone top site should have the right hostname."
  );

  // Reset the DEFAULT_TOP_SITES;
  DEFAULT_TOP_SITES.length = 0;

  info("refreshDefaults should add no defaults on empty pref");
  TopSites.refreshDefaults("");
  Assert.equal(
    DEFAULT_TOP_SITES.length,
    0,
    "Should have 0 DEFAULT_TOP_SITES now."
  );

  info("refreshDefaults should be able to clear defaults");
  TopSites.refreshDefaults("https://foo.com");
  TopSites.refreshDefaults("");

  Assert.equal(
    DEFAULT_TOP_SITES.length,
    0,
    "Should have 0 DEFAULT_TOP_SITES now."
  );

  Services.prefs.clearUserPref(
    "browser.newtabpage.activity-stream.default.sites"
  );
  Services.prefs.clearUserPref(
    "browser.newtabpage.activity-stream.topSitesRows"
  );
  TopSites.uninit();
  sandbox.restore();
  await cleanup();
});

add_task(
  async function test_getLinksWithDefaults_on_SearchService_init_failure() {
    let sandbox = sinon.createSandbox();
    let cleanup = stubTopSites(sandbox);

    TopSites.refreshDefaults("https://foo.com");

    gSearchServiceInitStub.rejects(
      new Error("Simulating search init failures")
    );

    const result = await TopSites.getLinksWithDefaults();
    Assert.ok(result);

    gSearchServiceInitStub.resolves();

    sandbox.restore();
    await cleanup();
  }
);

add_task(async function test_getLinksWithDefaults() {
  NewTabUtils.activityStreamLinks.getTopSites.resetHistory();

  let sandbox = sinon.createSandbox();
  let cleanup = stubTopSites(sandbox);

  TopSites.refreshDefaults("https://foo.com");

  info("getLinksWithDefaults should get the links from NewTabUtils");
  let result = await TopSites.getLinksWithDefaults();

  const reference = FAKE_LINKS.map(site =>
    Object.assign({}, site, {
      hostname: NewTabUtils.shortURL(site),
      typedBonus: true,
    })
  );

  Assert.deepEqual(result, reference);
  Assert.ok(NewTabUtils.activityStreamLinks.getTopSites.calledOnce);

  info("getLinksWithDefaults should indicate the links get typed bonus");
  Assert.ok(result[0].typedBonus, "Expected typed bonus property to be true.");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_filterAdult() {
  let sandbox = sinon.createSandbox();
  info("getLinksWithDefaults should filter out non-pinned adult sites");

  sandbox.stub(FilterAdult, "filter").returns([]);
  const TEST_URL = "https://foo.com/";
  sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [{ url: TEST_URL }]);

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  const result = await TopSites.getLinksWithDefaults();
  Assert.ok(FilterAdult.filter.calledOnce);
  Assert.equal(result.length, 1);
  Assert.equal(result[0].url, TEST_URL);

  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_caching() {
  let sandbox = sinon.createSandbox();

  info(
    "getLinksWithDefaults should filter out the defaults that have been blocked"
  );
  // make sure we only have one top site, and we block the only default site we have to show
  const url = "www.myonlytopsite.com";
  const topsite = {
    frecency: FAKE_FRECENCY,
    hostname: NewTabUtils.shortURL({ url }),
    typedBonus: true,
    url,
  };

  const blockedDefaultSite = { url: "https://foo.com" };
  gGetTopSitesStub.resolves([topsite]);
  sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => {
    return site.url === blockedDefaultSite.url;
  });

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");
  const result = await TopSites.getLinksWithDefaults();

  // what we should be left with is just the top site we added, and not the default site we blocked
  Assert.equal(result.length, 1);
  Assert.deepEqual(result[0], topsite);
  let foundBlocked = result.find(site => site.url === blockedDefaultSite.url);
  Assert.ok(!foundBlocked, "Should not have found blocked site.");

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_dedupe() {
  let sandbox = sinon.createSandbox();

  info("getLinksWithDefaults should call dedupe.group on the links");
  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  let stub = sandbox.stub(TopSites.dedupe, "group").callsFake((...id) => id);
  await TopSites.getLinksWithDefaults();

  Assert.ok(stub.calledOnce, "dedupe.group was called once");
  sandbox.restore();
  await cleanup();
});

add_task(async function test__dedupe_key() {
  let sandbox = sinon.createSandbox();

  info("_dedupeKey should dedupe on hostname instead of url");
  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  let site = { url: "foo", hostname: "bar" };
  let result = TopSites._dedupeKey(site);

  Assert.equal(result, site.hostname, "deduped on hostname");
  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_adds_defaults() {
  let sandbox = sinon.createSandbox();

  info(
    "getLinksWithDefaults should add defaults if there are are not enough links"
  );
  const TEST_LINKS = [{ frecency: FAKE_FRECENCY, url: "foo.com" }];
  gGetTopSitesStub.resolves(TEST_LINKS);
  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  let result = await TopSites.getLinksWithDefaults();

  let reference = [...TEST_LINKS, ...DEFAULT_TOP_SITES].map(s =>
    Object.assign({}, s, {
      hostname: NewTabUtils.shortURL(s),
      typedBonus: true,
    })
  );

  Assert.deepEqual(result, reference);

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(
  async function test_getLinksWithDefaults_adds_defaults_for_visible_slots() {
    let sandbox = sinon.createSandbox();

    info(
      "getLinksWithDefaults should only add defaults up to the number of visible slots"
    );
    const numVisible = TOP_SITES_DEFAULT_ROWS * TOP_SITES_MAX_SITES_PER_ROW;
    let testLinks = [];
    for (let i = 0; i < numVisible - 1; i++) {
      testLinks.push({ frecency: FAKE_FRECENCY, url: `foo${i}.com` });
    }
    gGetTopSitesStub.resolves(testLinks);

    let cleanup = stubTopSites(sandbox);
    TopSites.refreshDefaults("https://foo.com");

    let result = await TopSites.getLinksWithDefaults();

    let reference = [...testLinks, DEFAULT_TOP_SITES[0]].map(s =>
      Object.assign({}, s, {
        hostname: NewTabUtils.shortURL(s),
        typedBonus: true,
      })
    );

    Assert.equal(result.length, numVisible);
    Assert.deepEqual(result, reference);

    gGetTopSitesStub.resolves(FAKE_LINKS);
    sandbox.restore();
    await cleanup();
  }
);

add_task(async function test_getLinksWithDefaults_no_throw_on_no_links() {
  let sandbox = sinon.createSandbox();

  info("getLinksWithDefaults should not throw if NewTabUtils returns null");
  gGetTopSitesStub.resolves(null);

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  await TopSites.getLinksWithDefaults();
  Assert.ok(true"getLinksWithDefaults did not throw");

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_get_more_on_request() {
  let sandbox = sinon.createSandbox();

  info("getLinksWithDefaults should get more if the user has asked for more");
  let testLinks = new Array(4 * TOP_SITES_MAX_SITES_PER_ROW)
    .fill(null)
    .map((v, i) => ({
      frecency: FAKE_FRECENCY,
      url: `http://www.site${i}.com`,
    }));
  gGetTopSitesStub.resolves(testLinks);

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  const TEST_ROWS = 3;
  Services.prefs.setIntPref(
    "browser.newtabpage.activity-stream.topSitesRows",
    TEST_ROWS
  );

  let result = await TopSites.getLinksWithDefaults();
  Assert.equal(result.length, TEST_ROWS * TOP_SITES_MAX_SITES_PER_ROW);

  Services.prefs.clearUserPref(
    "browser.newtabpage.activity-stream.topSitesRows"
  );
  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_reuse_cache() {
  let sandbox = sinon.createSandbox();
  info("getLinksWithDefaults should reuse the cache on subsequent calls");

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  gGetTopSitesStub.resetHistory();

  await TopSites.getLinksWithDefaults();
  await TopSites.getLinksWithDefaults();

  Assert.ok(
    NewTabUtils.activityStreamLinks.getTopSites.calledOnce,
    "getTopSites only called once"
  );

  sandbox.restore();
  await cleanup();
});

add_task(
  async function test_getLinksWithDefaults_ignore_cache_on_requesting_more() {
    let sandbox = sinon.createSandbox();
    info("getLinksWithDefaults should ignore the cache when requesting more");

    let cleanup = stubTopSites(sandbox);
    TopSites.refreshDefaults("https://foo.com");

    gGetTopSitesStub.resetHistory();

    await TopSites.getLinksWithDefaults();
    Services.prefs.setIntPref(
      "browser.newtabpage.activity-stream.topSitesRows",
      3
    );
    await TopSites.getLinksWithDefaults();

    Assert.ok(
      NewTabUtils.activityStreamLinks.getTopSites.calledTwice,
      "getTopSites called twice"
    );

    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.topSitesRows"
    );
    sandbox.restore();
    await cleanup();
  }
);

add_task(
  async function test_getLinksWithDefaults_migrate_pinned_favicon_data() {
    let sandbox = sinon.createSandbox();
    info(
      "getLinksWithDefaults should migrate pinned favicon data without getting favicons again"
    );

    let cleanup = stubTopSites(sandbox);
    TopSites.refreshDefaults("https://foo.com");

    gGetTopSitesStub.resetHistory();

    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [{ url: "https://foo.com/" }]);

    await TopSites.getLinksWithDefaults();

    let originalCallCount =
      NewTabUtils.activityStreamProvider._addFavicons.callCount;
    TopSites.pinnedCache.expire();

    let result = await TopSites.getLinksWithDefaults();

    Assert.equal(
      NewTabUtils.activityStreamProvider._addFavicons.callCount,
      originalCallCount,
      "_addFavicons was not called again."
    );
    Assert.equal(result[0].favicon, FAKE_FAVICON);
    Assert.equal(result[0].faviconSize, FAKE_FAVICON_SIZE);

    sandbox.restore();
    await cleanup();
  }
);

add_task(async function test_getLinksWithDefaults_no_internal_properties() {
  let sandbox = sinon.createSandbox();
  info("getLinksWithDefaults should not expose internal link properties");

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  let result = await TopSites.getLinksWithDefaults();

  let internal = Object.keys(result[0]).filter(key => key.startsWith("__"));
  Assert.equal(internal.join(""), "");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_copy_frecent_screenshot() {
  // TopSites pulls data from NewTabUtils.activityStreamLinks.getTopSites()
  // which can still pass screenshots to it if they are available.
  let sandbox = sinon.createSandbox();
  info(
    "getLinksWithDefaults should copy the screenshot of the frecent site if it exists"
  );

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  const TEST_SCREENSHOT = "screenshot";

  gGetTopSitesStub.resolves([
    { url: "https://foo.com/", screenshot: TEST_SCREENSHOT },
  ]);
  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [{ url: "https://foo.com/" }]);

  let result = await TopSites.getLinksWithDefaults();

  Assert.equal(result[0].screenshot, TEST_SCREENSHOT);

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_copies_both_screenshots() {
  // TopSites pulls data from NewTabUtils.activityStreamLinks.getTopSites()
  // and NewTabUtils.pinnedLinks which can pass screenshot data to it if they
  // are available.
  let sandbox = sinon.createSandbox();
  info(
    "getLinksWithDefaults should still copy the frecent screenshot if " +
      "customScreenshotURL is set"
  );

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  gGetTopSitesStub.resolves([
    { url: "https://foo.com/", screenshot: "screenshot" },
  ]);
  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [{ url: "https://foo.com/", customScreenshotURL: "custom" }]);

  let result = await TopSites.getLinksWithDefaults();

  Assert.equal(result[0].screenshot, "screenshot");
  Assert.equal(result[0].customScreenshotURL, "custom");

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_persist_screenshot() {
  let sandbox = sinon.createSandbox();
  info(
    "getLinksWithDefaults should keep the same screenshot if no frecent site is found"
  );

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  const CUSTOM_SCREENSHOT = "custom";

  gGetTopSitesStub.resolves([]);
  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [{ url: "https://foo.com/", screenshot: CUSTOM_SCREENSHOT }]);

  let result = await TopSites.getLinksWithDefaults();

  Assert.equal(result[0].screenshot, CUSTOM_SCREENSHOT);

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(
  async function test_getLinksWithDefaults_no_overwrite_pinned_screenshot() {
    let sandbox = sinon.createSandbox();
    info("getLinksWithDefaults should not overwrite pinned site screenshot");

    let cleanup = stubTopSites(sandbox);
    TopSites.refreshDefaults("https://foo.com");

    const EXISTING_SCREENSHOT = "some-screenshot";

    gGetTopSitesStub.resolves([{ url: "https://foo.com/", screenshot: "foo" }]);
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [
        { url: "https://foo.com/", screenshot: EXISTING_SCREENSHOT },
      ]);

    let result = await TopSites.getLinksWithDefaults();

    Assert.equal(result[0].screenshot, EXISTING_SCREENSHOT);

    gGetTopSitesStub.resolves(FAKE_LINKS);
    sandbox.restore();
    await cleanup();
  }
);

add_task(
  async function test_getLinksWithDefaults_no_searchTopSite_from_frecent() {
    let sandbox = sinon.createSandbox();
    info("getLinksWithDefaults should not set searchTopSite from frecent site");

    let cleanup = stubTopSites(sandbox);
    TopSites.refreshDefaults("https://foo.com");

    const EXISTING_SCREENSHOT = "some-screenshot";

    gGetTopSitesStub.resolves([
      {
        url: "https://foo.com/",
        searchTopSite: true,
        screenshot: EXISTING_SCREENSHOT,
      },
    ]);
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [{ url: "https://foo.com/" }]);

    let result = await TopSites.getLinksWithDefaults();

    Assert.ok(!result[0].searchTopSite);
    // But it should copy over other properties
    Assert.equal(result[0].screenshot, EXISTING_SCREENSHOT);

    gGetTopSitesStub.resolves(FAKE_LINKS);
    sandbox.restore();
    await cleanup();
  }
);

add_task(async function test_getLinksWithDefaults_concurrency_getTopSites() {
  let sandbox = sinon.createSandbox();
  info(
    "getLinksWithDefaults concurrent calls should call the backing data once"
  );

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  NewTabUtils.activityStreamLinks.getTopSites.resetHistory();

  await Promise.all([
    TopSites.getLinksWithDefaults(),
    TopSites.getLinksWithDefaults(),
  ]);

  Assert.ok(
    NewTabUtils.activityStreamLinks.getTopSites.calledOnce,
    "getTopSites only called once"
  );

  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_deduping_no_dedupe_pinned() {
  let sandbox = sinon.createSandbox();
  info("getLinksWithDefaults should not dedupe pinned sites");

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults("https://foo.com");

  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [
      { url: "https://developer.mozilla.org/en-US/docs/Web" },
      { url: "https://developer.mozilla.org/en-US/docs/Learn" },
    ]);

  let sites = await TopSites.getLinksWithDefaults();
  Assert.equal(sites.length, 2 * TOP_SITES_MAX_SITES_PER_ROW);
  Assert.equal(sites[0].url, NewTabUtils.pinnedLinks.links[0].url);
  Assert.equal(sites[1].url, NewTabUtils.pinnedLinks.links[1].url);
  Assert.equal(sites[0].hostname, sites[1].hostname);

  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_prefer_pinned_sites() {
  let sandbox = sinon.createSandbox();

  info("getLinksWithDefaults should prefer pinned sites over links");

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults();

  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [
      { url: "https://developer.mozilla.org/en-US/docs/Web" },
      { url: "https://developer.mozilla.org/en-US/docs/Learn" },
    ]);

  const SECOND_TOP_SITE_URL = "https://www.mozilla.org/";

  gGetTopSitesStub.resolves([
    { frecency: FAKE_FRECENCY, url: "https://developer.mozilla.org/" },
    { frecency: FAKE_FRECENCY, url: SECOND_TOP_SITE_URL },
  ]);

  let sites = await TopSites.getLinksWithDefaults();

  // Expecting 3 links where there's 2 pinned and 1 www.mozilla.org, so
  // the frecent with matching hostname as pinned is removed.
  Assert.equal(sites.length, 3);
  Assert.equal(sites[0].url, NewTabUtils.pinnedLinks.links[0].url);
  Assert.equal(sites[1].url, NewTabUtils.pinnedLinks.links[1].url);
  Assert.equal(sites[2].url, SECOND_TOP_SITE_URL);

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_title_and_null() {
  let sandbox = sinon.createSandbox();

  info("getLinksWithDefaults should return sites that have a title");

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults();

  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [{ url: "https://github.com/mozilla/activity-stream" }]);

  let sites = await TopSites.getLinksWithDefaults();
  for (let site of sites) {
    Assert.ok(site.hostname);
  }

  info("getLinksWithDefaults should not throw for null entries");
  sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [null]);
  await TopSites.getLinksWithDefaults();
  Assert.ok(true"getLinksWithDefaults didn't throw");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_getLinksWithDefaults_calls__fetchIcon() {
  let sandbox = sinon.createSandbox();

  info("getLinksWithDefaults should return sites that have a title");

  let cleanup = stubTopSites(sandbox);
  TopSites.refreshDefaults();

  sandbox.spy(TopSites, "_fetchIcon");
  let results = await TopSites.getLinksWithDefaults();
  Assert.ok(results.length, "Got back some results");
  Assert.equal(TopSites._fetchIcon.callCount, results.length);
  for (let result of results) {
    Assert.ok(TopSites._fetchIcon.calledWith(result));
  }

  sandbox.restore();
  await cleanup();
});

add_task(async function test_init() {
  let sandbox = sinon.createSandbox();

  sandbox.stub(NimbusFeatures.newtab, "onUpdate");

  let cleanup = stubTopSites(sandbox);

  sandbox.stub(TopSites, "refresh");
  await TopSites.init();

  info("TopSites.init should call refresh");
  Assert.ok(TopSites.refresh.calledOnce, "refresh called once");
  Assert.ok(
    TopSites.refresh.calledWithExactly({
      isStartup: true,
    })
  );

  TopSites.uninit();
  sandbox.restore();
  await cleanup();
});

/**
 * If multiple callers are attempting to initializing TopSites, we should
 * initialize only once and wait until its completed.
 */

add_task(async function test_multiple_init() {
  info("Initing TopSites multiple times should call _readDefaults only once.");
  let sandbox = sinon.createSandbox();
  sandbox.stub(NimbusFeatures.newtab, "onUpdate");
  sandbox.stub(TopSites, "_readDefaults");
  let cleanup = stubTopSites(sandbox);

  Assert.ok(TopSites._readDefaults.notCalled, "Read defaults not called.");
  for (let i = 0; i < 5; ++i) {
    await TopSites.init();
  }
  Assert.ok(TopSites._readDefaults.calledOnce, "Read defaults called once.");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_multiple_init_delay() {
  TopSites.uninit();

  info(
    "Initing TopSites multiple times should allow callers " +
      "only call readDefaults once and wait until its finished."
  );
  let sandbox = sinon.createSandbox();

  let resolvePromise;
  let promise = new Promise(resolve => {
    resolvePromise = resolve;
  });

  sandbox.stub(NimbusFeatures.newtab, "onUpdate");
  sandbox.stub(TopSites, "_readDefaults").returns(promise);
  let cleanup = stubTopSites(sandbox);

  Assert.ok(TopSites._readDefaults.notCalled, "Read defaults not called.");
  let finishedPromiseCount = 0;
  let promises = [];
  let callInit = async () => {
    await TopSites.init();
    ++finishedPromiseCount;
  };
  for (let i = 0; i < 5; ++i) {
    promises.push(callInit());
  }
  Assert.equal(
    finishedPromiseCount,
    0,
    "Finished promise count should be equal."
  );
  Assert.ok(TopSites._readDefaults.calledOnce, "Read defaults called once.");

  info("Resolve the promises.");
  resolvePromise();
  await Promise.all(promises);
  Assert.equal(
    finishedPromiseCount,
    5,
    "Finished promise count should be equal."
  );
  Assert.ok(
    TopSites._readDefaults.calledOnce,
    "Read defaults was still only called once."
  );

  sandbox.restore();
  await cleanup();
});

add_task(async function test_uninit() {
  info("Un-initing TopSites should expire caches.");
  let sandbox = sinon.createSandbox();

  let cleanup = stubTopSites(sandbox);
  sandbox.stub(TopSites, "refresh");
  await TopSites.init();

  sandbox.stub(TopSites.pinnedCache, "expire");
  sandbox.stub(TopSites.frecentCache, "expire");
  TopSites.uninit();

  Assert.ok(
    TopSites.pinnedCache.expire.calledOnce,
    "pinnedCache.expire called once"
  );
  Assert.ok(
    TopSites.frecentCache.expire.calledOnce,
    "frecentCache.expire called once"
  );

  sandbox.restore();
  await cleanup();
});

add_task(async function test_get_sites_init() {
  info("TopSites.getSites should initialize TopSites if its not inited.");
  let sandbox = sinon.createSandbox();

  let cleanup = stubTopSites(sandbox);
  sandbox.stub(TopSites, "init");

  Assert.ok(TopSites.init.notCalled, "TopSites.init not called.");
  await TopSites.getSites();
  Assert.ok(TopSites.init.calledOnce, "TopSites.init called once.");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_get_sites_already_inited() {
  info(
    "TopSites.getSites should not call related initialization methods " +
      "more than once if TopSites is already inited."
  );
  let sandbox = sinon.createSandbox();

  let cleanup = stubTopSites(sandbox);
  sandbox.spy(TopSites, "_readDefaults");
  await TopSites.init();

  Assert.ok(
    TopSites._readDefaults.calledOnce,
    "TopSites._readDefaults called once."
  );
  Assert.ok(
    TopSites.updateCustomSearchShortcuts.calledOnce,
    "TopSites.updateCustomSearchShortcuts called once."
  );
  await TopSites.getSites();
  Assert.ok(
    TopSites._readDefaults.calledOnce,
    "TopSites._readDefaults still only called once."
  );
  Assert.ok(
    TopSites.updateCustomSearchShortcuts.calledOnce,
    "TopSites.updateCustomSearchShortcuts still only called once."
  );

  sandbox.restore();
  await cleanup();
});

add_task(async function test_get_sites_delayed_init() {
  info("TopSites.getSites should wait until initialization is done.");
  let sandbox = sinon.createSandbox();

  let cleanup = stubTopSites(sandbox);

  // Ensure it's not initialized.
  TopSites.uninit();

  let resolvePromise;
  let promise = new Promise(resolve => {
    resolvePromise = resolve;
  });
  sandbox.stub(TopSites, "init").returns(promise);

  let promises = [];
  let finishedPromiseCount = 0;
  let callGetSites = async () => {
    await TopSites.getSites();
    finishedPromiseCount += 1;
  };
  for (let i = 0; i < 5; ++i) {
    promises.push(callGetSites());
  }

  Assert.equal(
    finishedPromiseCount,
    0,
    "All calls to TopSites.getSites() haven't finished."
  );
  resolvePromise();
  await Promise.all(promises);
  Assert.equal(
    finishedPromiseCount,
    5,
    "All calls to TopSites.getSites() finished."
  );

  sandbox.restore();
  await cleanup();
});

add_task(async function test_refresh() {
  let sandbox = sinon.createSandbox();

  sandbox.stub(NimbusFeatures.newtab, "onUpdate");

  let cleanup = stubTopSites(sandbox);

  sandbox.stub(TopSites, "_fetchIcon");
  TopSites._startedUp = true;

  info("TopSites.refresh should wait for tippytop to initialize");
  TopSites._tippyTopProvider.initialized = false;
  sandbox.stub(TopSites._tippyTopProvider, "init").resolves();

  await TopSites.refresh();

  Assert.ok(
    TopSites._tippyTopProvider.init.calledOnce,
    "TopSites._tippyTopProvider.init called once"
  );

  info(
    "TopSites.refresh should not init the tippyTopProvider if already initialized"
  );
  TopSites._tippyTopProvider.initialized = true;
  TopSites._tippyTopProvider.init.resetHistory();

  await TopSites.refresh();

  Assert.ok(
    TopSites._tippyTopProvider.init.notCalled,
    "tippyTopProvider not initted again"
  );

  sandbox.restore();
  await cleanup();
});

add_task(async function test_refresh_updateTopSites() {
  // Ensure that TopSites isn't already initialized.
  TopSites.uninit();

  let sandbox = sinon.createSandbox();
  let cleanup = stubTopSites(sandbox);

  await TopSites.init();
  TopSites._reset();

  // Clear the internal store.
  TopSites._reset();

  let sites = await TopSites.getSites();
  Assert.equal(sites.length, 0, "Sites is empty.");

  info("TopSites.refresh should update TopSites.sites");
  let promise = TestUtils.topicObserved("topsites-refreshed");
  // TODO: On New Tab, subscribe to updates to Top Sites.
  await TopSites.refresh({ isStartup: true });
  await promise;

  sites = await TopSites.getSites();
  Assert.ok(sites.length, "Sites has values.");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_refresh_dispatch() {
  let sandbox = sinon.createSandbox();

  info("TopSites.refresh should dispatch an action with the links returned");

  let cleanup = stubTopSites(sandbox);
  sandbox.stub(TopSites, "_fetchIcon");
  TopSites._startedUp = true;

  await TopSites.refresh();
  let reference = FAKE_LINKS.map(site =>
    Object.assign({}, site, {
      hostname: NewTabUtils.shortURL(site),
      typedBonus: true,
    })
  );

  // TODO: On New Tab, subscribe to updates to Top Sites.
  let sites = await TopSites.getSites();
  Assert.deepEqual(sites, reference, "Sites are updated.");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_refresh_empty_slots() {
  let sandbox = sinon.createSandbox();

  info(
    "TopSites.refresh should handle empty slots in the resulting top sites array"
  );

  let cleanup = stubTopSites(sandbox);
  sandbox.stub(TopSites, "_fetchIcon");
  TopSites._startedUp = true;

  gGetTopSitesStub.resolves([FAKE_LINKS[0]]);
  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [
      null,
      null,
      FAKE_LINKS[1],
      null,
      null,
      null,
      null,
      null,
      FAKE_LINKS[2],
    ]);
  await TopSites.refresh();

  let reference = FAKE_LINKS.map(site =>
    Object.assign({}, site, {
      hostname: NewTabUtils.shortURL(site),
      typedBonus: true,
    })
  );
  const expected = [
    reference[0],
    null,
    createExpectedPinnedLink(reference[1], 2),
    null,
    null,
    null,
    null,
    null,
    createExpectedPinnedLink(reference[2], 8),
  ];

  // TODO: On New Tab, subscribe to updates to Top Sites.
  let sites = await TopSites.getSites();
  assertLinks(sites, expected);

  gGetTopSitesStub.resolves(FAKE_LINKS);
  sandbox.restore();
  await cleanup();
});

add_task(async function test_onAction_part_2() {
  let sandbox = sinon.createSandbox();

  info(
    "TopSites.handlePlacesEvents should call refresh without a target " +
      "if we clear history."
  );

  let cleanup = stubTopSites(sandbox);
  sandbox.stub(TopSites, "refresh");
  TopSites.refresh.resetHistory();
  await PlacesUtils.history.clear();
  Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once");

  TopSites.refresh.resetHistory();

  info(
    "TopSites.handlePlacesEvents should call refresh without a target " +
      "if we remove a Topsite from history"
  );
  let uri = Services.io.newURI("https://www.example.com/");
  await PlacesTestUtils.addVisits({
    uri,
    transition: PlacesUtils.history.TRANSITION_TYPED,
    visitDate: Date.now() * 1000,
  });
  Assert.ok(TopSites.refresh.notCalled, "TopSites.refresh not called");
  await PlacesUtils.history.remove(uri);

  Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once");

  info("TopSites.handlePlacesEvents should call refresh on newtab-linkBlocked");
  TopSites.refresh.resetHistory();
  // The event dispatched in NewTabUtils when a link is blocked;
  TopSites.observe(null"newtab-linkBlocked"null);
  Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once");

  info("TopSites should call refresh on bookmark-added");
  TopSites.refresh.resetHistory();
  let bookmark = await PlacesUtils.bookmarks.insert({
    url: "https://bookmark.example.com",
    title: "Bookmark 1",
    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
  });
  Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once");

  info("TopSites.onAction should call refresh on bookmark-removed");
  TopSites.refresh.resetHistory();
  await PlacesUtils.bookmarks.remove(bookmark);
  Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once");

  info(
    "TopSites.onAction should call pin with correct args on " +
      "TOP_SITES_INSERT without an index specified"
  );
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");

  let addAction = {
    type: at.TOP_SITES_INSERT,
    data: { site: { url: "foo.bar", label: "foo" } },
  };
  TopSites.insert(addAction);
  Assert.ok(
    NewTabUtils.pinnedLinks.pin.calledOnce,
    "NewTabUtils.pinnedLinks.pin called once"
  );
  Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(addAction.data.site, 0));

  info(
    "TopSites.onAction should call pin with correct args on " +
      "TOP_SITES_INSERT"
  );
  NewTabUtils.pinnedLinks.pin.resetHistory();
  let dropAction = {
    type: at.TOP_SITES_INSERT,
    data: { site: { url: "foo.bar", label: "foo" }, index: 3 },
  };
  TopSites.insert(dropAction);
  Assert.ok(
    NewTabUtils.pinnedLinks.pin.calledOnce,
    "NewTabUtils.pinnedLinks.pin called once"
  );
  Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(dropAction.data.site, 3));

  sandbox.restore();
  await cleanup();
});

add_task(async function test_insert_part_1() {
  let sandbox = sinon.createSandbox();
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");
  let cleanup = stubTopSites(sandbox);

  {
    info(
      "TopSites.insert should pin site in first slot of pinned list with " +
        "empty first slot"
    );

    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [null, { url: "example.com" }]);
    let site = { url: "foo.bar", label: "foo" };
    await TopSites.insert({ data: { site } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledOnce,
      "NewTabUtils.pinnedLinks.pin called once"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0));
    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  {
    info(
      "TopSites.insert should move a pinned site in first slot to the " +
        "next slot: part 1"
    );
    let site1 = { url: "example.com" };
    sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [site1]);
    let site = { url: "foo.bar", label: "foo" };

    await TopSites.insert({ data: { site } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledTwice,
      "NewTabUtils.pinnedLinks.pin called twice"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1));
    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  {
    info(
      "TopSites.insert should move a pinned site in first slot to the " +
        "next slot: part 2"
    );
    let site1 = { url: "example.com" };
    let site2 = { url: "example.org" };
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [site1, null, site2]);
    let site = { url: "foo.bar", label: "foo" };
    await TopSites.insert({ data: { site } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledTwice,
      "NewTabUtils.pinnedLinks.pin called twice"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1));
    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  sandbox.restore();
  await cleanup();
});

add_task(async function test_insert_part_2() {
  let sandbox = sinon.createSandbox();
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");
  let cleanup = stubTopSites(sandbox);

  {
    info(
      "TopSites.insert should unpin the last site if all slots are " +
        "already pinned"
    );
    let site1 = { url: "example.com" };
    let site2 = { url: "example.org" };
    let site3 = { url: "example.net" };
    let site4 = { url: "example.biz" };
    let site5 = { url: "example.info" };
    let site6 = { url: "example.news" };
    let site7 = { url: "example.lol" };
    let site8 = { url: "example.golf" };
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [site1, site2, site3, site4, site5, site6, site7, site8]);
    Services.prefs.setIntPref(
      "browser.newtabpage.activity-stream.topSitesRows",
      1
    );
    let site = { url: "foo.bar", label: "foo" };
    await TopSites.insert({ data: { site } });
    Assert.equal(
      NewTabUtils.pinnedLinks.pin.callCount,
      8,
      "NewTabUtils.pinnedLinks.pin called 8 times"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 1));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 2));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site3, 3));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site4, 4));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site5, 5));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site6, 6));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site7, 7));
    NewTabUtils.pinnedLinks.pin.resetHistory();
    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.topSitesRows"
    );
  }

  {
    info("TopSites.insert should trigger refresh on TOP_SITES_INSERT");
    sandbox.stub(TopSites, "refresh");
    let addAction = {
      type: at.TOP_SITES_INSERT,
      data: { site: { url: "foo.com" } },
    };

    await TopSites.insert(addAction);

    Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once");
  }

  {
    info("TopSites.insert should correctly handle different index values");
    let index = -1;
    let site = { url: "foo.bar", label: "foo" };
    let action = { data: { index, site } };

    await TopSites.insert(action);
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0));

    index = undefined;
    await TopSites.insert(action);
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 0));

    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  sandbox.restore();
  await cleanup();
});

add_task(async function test_insert_part_3() {
  let sandbox = sinon.createSandbox();
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");
  let cleanup = stubTopSites(sandbox);

  {
    info("TopSites.insert should pin site in specified slot that is free");
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [null, { url: "example.com" }]);

    let site = { url: "foo.bar", label: "foo" };

    await TopSites.insert({ data: { index: 2, site, draggedFromIndex: 0 } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledOnce,
      "NewTabUtils.pinnedLinks.pin called once"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2));

    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  {
    info(
      "TopSites.insert should move a pinned site in specified slot " +
        "to the next slot"
    );
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [nullnull, { url: "example.com" }]);

    let site = { url: "foo.bar", label: "foo" };

    await TopSites.insert({ data: { index: 2, site, draggedFromIndex: 3 } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledTwice,
      "NewTabUtils.pinnedLinks.pin called twice"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2));
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledWith({ url: "example.com" }, 3)
    );

    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  {
    info(
      "TopSites.insert should move pinned sites in the direction " +
        "of the dragged site"
    );

    let site1 = { url: "foo.bar", label: "foo" };
    let site2 = { url: "example.com", label: "example" };
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [nullnull, site2]);

    await TopSites.insert({
      data: { index: 2, site: site1, draggedFromIndex: 0 },
    });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledTwice,
      "NewTabUtils.pinnedLinks.pin called twice"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 2));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 1));
    NewTabUtils.pinnedLinks.pin.resetHistory();

    await TopSites.insert({
      data: { index: 2, site: site1, draggedFromIndex: 5 },
    });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledTwice,
      "NewTabUtils.pinnedLinks.pin called twice"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site1, 2));
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site2, 3));
    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  {
    info("TopSites.insert should not insert past the visible top sites");
    let site1 = { url: "foo.bar", label: "foo" };
    await TopSites.insert({
      data: { index: 42, site: site1, draggedFromIndex: 0 },
    });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.notCalled,
      "NewTabUtils.pinnedLinks.pin wasn't called"
    );

    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  sandbox.restore();
  await cleanup();
});

add_task(async function test_pin_part_1() {
  let sandbox = sinon.createSandbox();
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");
  sandbox.spy(TopSites.pinnedCache, "request");
  let cleanup = stubTopSites(sandbox);

  {
    info("TopSites.pin should pin site in specified slot empty pinned list");
    let site = {
      url: "foo.bar",
      label: "foo",
    };
    Assert.ok(
      TopSites.pinnedCache.request.notCalled,
      "TopSites.pinnedCache.request not called"
    );
    await TopSites.pin({ data: { index: 2, site } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.called,
      "NewTabUtils.pinnedLinks.pin called"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2));
    NewTabUtils.pinnedLinks.pin.resetHistory();
    TopSites.pinnedCache.request.resetHistory();
  }

  {
    info(
      "TopSites.pin should not do a link object lookup if custom " +
        "screenshot field is not set"
    );
    let site = { url: "foo.bar", label: "foo" };
    await TopSites.pin({ data: { index: 2, site } });
    Assert.ok(
      !TopSites.pinnedCache.request.called,
      "TopSites.pinnedCache.request never called"
    );
    NewTabUtils.pinnedLinks.pin.resetHistory();
    TopSites.pinnedCache.request.resetHistory();
  }

  {
    info(
      "TopSites.pin should pin site in specified slot of pinned " +
        "list that is free"
    );
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [null, { url: "example.com" }]);

    let site = { url: "foo.bar", label: "foo" };
    await TopSites.pin({ data: { index: 2, site } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledOnce,
      "NewTabUtils.pinnedLinks.pin called once"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2));
    NewTabUtils.pinnedLinks.pin.resetHistory();
  }

  sandbox.restore();
  await cleanup();
});

add_task(async function test_pin_part_2() {
  let sandbox = sinon.createSandbox();
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");

  {
    info("TopSites.pin should save the searchTopSite attribute if set");
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [null, { url: "example.com" }]);

    let site = { url: "foo.bar", label: "foo", searchTopSite: true };
    let cleanup = stubTopSites(sandbox);
    await TopSites.pin({ data: { index: 2, site } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledOnce,
      "NewTabUtils.pinnedLinks.pin called once"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.firstCall.args[0].searchTopSite);
    NewTabUtils.pinnedLinks.pin.resetHistory();
    await cleanup();
  }

  {
    info(
      "TopSites.pin should NOT move a pinned site in specified " +
        "slot to the next slot"
    );
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [nullnull, { url: "example.com" }]);

    let site = { url: "foo.bar", label: "foo" };
    let cleanup = stubTopSites(sandbox);
    await TopSites.pin({ data: { index: 2, site } });
    Assert.ok(
      NewTabUtils.pinnedLinks.pin.calledOnce,
      "NewTabUtils.pinnedLinks.pin called once"
    );
    Assert.ok(NewTabUtils.pinnedLinks.pin.calledWith(site, 2));
    NewTabUtils.pinnedLinks.pin.resetHistory();
    await cleanup();
  }

  {
    info(
      "TopSites.pin should not update LinksCache object with screenshot data" +
        "properties between migrations"
    );
    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [{ url: "https://foo.com/" }]);

    let cleanup = stubTopSites(sandbox);
    let pinnedLinks = await TopSites.pinnedCache.request();
    Assert.equal(pinnedLinks.length, 1);
    TopSites.pinnedCache.expire();

    pinnedLinks[0].__sharedCache.updateLink("screenshot""foo");

    pinnedLinks = await TopSites.pinnedCache.request();
    Assert.equal(pinnedLinks[0].screenshot, undefined);

    // Force cache expiration in order to trigger a migration of objects
    TopSites.pinnedCache.expire();
    pinnedLinks[0].__sharedCache.updateLink("screenshot""bar");

    pinnedLinks = await TopSites.pinnedCache.request();
    Assert.equal(pinnedLinks[0].screenshot, undefined);
    await cleanup();
  }

  sandbox.restore();
});

add_task(async function test_pin_part_3() {
  let sandbox = sinon.createSandbox();
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");
  sandbox.spy(TopSites, "insert");

  {
    info("TopSites.pin should call insert if index < 0");
    let site = { url: "foo.bar", label: "foo" };
    let action = { data: { index: -1, site } };
    let cleanup = stubTopSites(sandbox);
    await TopSites.pin(action);

    Assert.ok(TopSites.insert.calledOnce, "TopSites.insert called once");
    Assert.ok(TopSites.insert.calledWithExactly(action));
    NewTabUtils.pinnedLinks.pin.resetHistory();
    TopSites.insert.resetHistory();
    await cleanup();
  }

  {
    info("TopSites.pin should not call insert if index == 0");
    let site = { url: "foo.bar", label: "foo" };
    let action = { data: { index: 0, site } };
    let cleanup = stubTopSites(sandbox);
    await TopSites.pin(action);

    Assert.ok(!TopSites.insert.called, "TopSites.insert not called");
    NewTabUtils.pinnedLinks.pin.resetHistory();
    await cleanup();
  }

  {
    info("TopSites.pin should trigger refresh on TOP_SITES_PIN");
    let cleanup = stubTopSites(sandbox);
    sandbox.stub(TopSites, "refresh");
    let pinExistingAction = {
      type: at.TOP_SITES_PIN,
      data: { site: FAKE_LINKS[4], index: 4 },
    };

    await TopSites.pin(pinExistingAction);

    Assert.ok(TopSites.refresh.calledOnce, "TopSites.refresh called once");
    NewTabUtils.pinnedLinks.pin.resetHistory();
    await cleanup();
  }

  sandbox.restore();
});

add_task(async function test_pin_part_4() {
  let sandbox = sinon.createSandbox();
  let cleanup = stubTopSites(sandbox);

  info("TopSites.pin should call with correct parameters on TOP_SITES_PIN");
  sandbox.stub(NewTabUtils.pinnedLinks, "pin");
  sandbox.spy(TopSites, "pin");

  let pinAction = {
    type: at.TOP_SITES_PIN,
    data: { site: { url: "foo.com" }, index: 7 },
  };
  await TopSites.pin(pinAction);
  Assert.ok(
    NewTabUtils.pinnedLinks.pin.calledOnce,
    "NewTabUtils.pinnedLinks.pin called once"
  );
  Assert.ok(
    NewTabUtils.pinnedLinks.pin.calledWithExactly(
      pinAction.data.site,
      pinAction.data.index
    )
  );
  Assert.ok(
    TopSites.pin.calledOnce,
    "TopSites.pin should call pin on TOP_SITES_PIN"
  );

  info(
    "TopSites.pin should unblock a previously blocked top site if " +
      "we are now adding it manually via 'Add a Top Site' option"
  );
  sandbox.stub(NewTabUtils.blockedLinks, "unblock");
  pinAction = {
    type: at.TOP_SITES_PIN,
    data: { site: { url: "foo.com" }, index: -1 },
  };
  await TopSites.pin(pinAction);
  Assert.ok(
    NewTabUtils.blockedLinks.unblock.calledWith({
      url: pinAction.data.site.url,
    })
  );

  info("TopSites.pin should call insert on TOP_SITES_INSERT");
  sandbox.stub(TopSites, "insert");
  let addAction = {
    type: at.TOP_SITES_INSERT,
    data: { site: { url: "foo.com" } },
  };

  await TopSites.pin(addAction);
  Assert.ok(TopSites.insert.calledOnce, "TopSites.insert called once");

  info(
    "TopSites.unpin should call unpin with correct parameters " +
      "on TOP_SITES_UNPIN"
  );

  sandbox
    .stub(NewTabUtils.pinnedLinks, "links")
    .get(() => [
      null,
      null,
      { url: "foo.com" },
      null,
      null,
      null,
      null,
      null,
      FAKE_LINKS[0],
    ]);
  sandbox.stub(NewTabUtils.pinnedLinks, "unpin");

  let unpinAction = {
    type: at.TOP_SITES_UNPIN,
    data: { site: { url: "foo.com" } },
  };
  await TopSites.unpin(unpinAction);
  Assert.ok(
    NewTabUtils.pinnedLinks.unpin.calledOnce,
    "NewTabUtils.pinnedLinks.unpin called once"
  );
  Assert.ok(NewTabUtils.pinnedLinks.unpin.calledWith(unpinAction.data.site));

  sandbox.restore();
  await cleanup();
});

add_task(async function test_integration() {
  let sandbox = sinon.createSandbox();

  info("Test adding a pinned site and removing it with actions");
  let cleanup = stubTopSites(sandbox);

  TopSites._startedUp = true;

  TopSites._requestRichIcon = sandbox.stub();
  let url = "https://pin.me";
  sandbox.stub(NewTabUtils.pinnedLinks, "pin").callsFake(link => {
    NewTabUtils.pinnedLinks.links.push(link);
  });

  await TopSites.insert({ type: at.TOP_SITES_INSERT, data: { site: { url } } });
  await TestUtils.topicObserved("topsites-refreshed");
  let oldSites = await TopSites.getSites();
  NewTabUtils.pinnedLinks.links.pop();
  // The event dispatched in NewTabUtils when a link is blocked;
  TopSites.observe(null"newtab-linkBlocked"null);
  await TestUtils.topicObserved("topsites-refreshed");
  let newSites = await TopSites.getSites();

  Assert.equal(oldSites[0].url, url, "Url matches.");
  Assert.equal(newSites[0].url, FAKE_LINKS[0].url, "Url matches.");

  sandbox.restore();
  await cleanup();
});

add_task(async function test_improvesearch_noDefaultSearchTile_experiment() {
  let sandbox = sinon.createSandbox();

  sandbox.stub(SearchService.prototype, "getDefault").resolves({
    identifier: "google",
  });

  {
    info(
      "TopSites.getLinksWithDefaults should filter out alexa top 5 " +
        "search from the default sites"
    );
    let cleanup = stubTopSites(sandbox);
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
      true
    );
    let top5Test = [
      "https://google.com",
      "https://search.yahoo.com",
      "https://yahoo.com",
      "https://bing.com",
      "https://ask.com",
      "https://duckduckgo.com",
    ];

    gGetTopSitesStub.resolves([
      { url: "https://amazon.com" },
      ...top5Test.map(url => ({ url })),
    ]);

    const urlsReturned = (await TopSites.getLinksWithDefaults()).map(
      link => link.url
    );
    Assert.ok(
      urlsReturned.includes("https://amazon.com"),
      "amazon included in default links"
    );
    top5Test.forEach(url =>
      Assert.ok(!urlsReturned.includes(url), `Should not include ${url}`)
    );

    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile"
    );
    gGetTopSitesStub.resolves(FAKE_LINKS);
    await cleanup();
  }

  {
    info(
      "TopSites.getLinksWithDefaults should not filter out alexa, default " +
        "search from the query results if the experiment pref is off"
    );
    let cleanup = stubTopSites(sandbox);
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
      false
    );
    gGetTopSitesStub.resolves([
      { url: "https://google.com" },
      { url: "https://foo.com" },
      { url: "https://duckduckgo" },
    ]);
    let urlsReturned = (await TopSites.getLinksWithDefaults()).map(
      link => link.url
    );

    Assert.ok(urlsReturned.includes("https://google.com"));
    gGetTopSitesStub.resolves(FAKE_LINKS);
    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile"
    );
    await cleanup();
  }

  {
    info(
      "TopSites.getLinksWithDefaults should filter out the current " +
        "default search from the default sites"
    );
    let cleanup = stubTopSites(sandbox);
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
      true
    );

    sandbox.stub(TopSites, "_currentSearchHostname").get(() => "amazon");
    Services.prefs.setStringPref(
      "browser.newtabpage.activity-stream.default.sites",
      "https://google.com,https://amazon.com"
    );
    gGetTopSitesStub.resolves([{ url: "https://foo.com" }]);

    let urlsReturned = (await TopSites.getLinksWithDefaults()).map(
      link => link.url
    );
    Assert.ok(!urlsReturned.includes("https://amazon.com"));

    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile"
    );
    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.default.sites"
    );
    gGetTopSitesStub.resolves(FAKE_LINKS);
    await cleanup();
  }

  {
    info(
      "TopSites.getLinksWithDefaults should not filter out current " +
        "default search from pinned sites even if it matches the current " +
        "default search"
    );
    let cleanup = stubTopSites(sandbox);
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
      true
    );

    sandbox
      .stub(NewTabUtils.pinnedLinks, "links")
      .get(() => [{ url: "google.com" }]);
    gGetTopSitesStub.resolves([{ url: "https://foo.com" }]);

    let urlsReturned = (await TopSites.getLinksWithDefaults()).map(
      link => link.url
    );
    Assert.ok(urlsReturned.includes("google.com"));

    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile"
    );
    gGetTopSitesStub.resolves(FAKE_LINKS);
    await cleanup();
  }

  sandbox.restore();
});

add_task(
  async function test_improvesearch_noDefaultSearchTile_experiment_part_2() {
    let sandbox = sinon.createSandbox();

    sandbox.stub(SearchService.prototype, "getDefault").resolves({
      identifier: "google",
    });

    sandbox.stub(TopSites, "refresh");

    {
      info(
        "TopSites.getLinksWithDefaults should call refresh and set " +
          "._currentSearchHostname to the new engine hostname when the " +
          "default search engine has been set"
      );
      let cleanup = stubTopSites(sandbox);
      Services.prefs.setBoolPref(
        "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
        true
      );

      TopSites.observe(
        null,
        "browser-search-engine-modified",
        "engine-default"
      );
      Assert.equal(TopSites._currentSearchHostname, "duckduckgo");
      // Refresh is called twice:
      // 1) For the change of `noDefaultSearchTile`
      // 2) Default search engine changed "browser-search-engine-modified"
      Assert.ok(TopSites.refresh.calledTwice, "TopSites.refresh called twice");

      Services.prefs.clearUserPref(
        "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile"
      );
      gGetTopSitesStub.resolves(FAKE_LINKS);
      TopSites.refresh.resetHistory();
      await cleanup();
    }

    {
      info(
        "TopSites.getLinksWithDefaults should call refresh when the " +
          "experiment pref has changed"
      );
      let cleanup = stubTopSites(sandbox);
      Services.prefs.setBoolPref(
        "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
        true
      );
      Assert.ok(
        TopSites.refresh.calledOnce,
        "TopSites.refresh was called once"
      );

      Services.prefs.setBoolPref(
        "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
        false
      );
      Assert.ok(
        TopSites.refresh.calledTwice,
        "TopSites.refresh was called twice"
      );

      Services.prefs.clearUserPref(
        "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile"
      );
      gGetTopSitesStub.resolves(FAKE_LINKS);
      TopSites.refresh.resetHistory();
      await cleanup();
    }

    sandbox.restore();
  }
);

// eslint-disable-next-line max-statements
add_task(async function test_improvesearch_topSitesSearchShortcuts() {
  let sandbox = sinon.createSandbox();
  let searchEngines = [{ aliases: ["@google"] }, { aliases: ["@amazon"] }];
  sandbox
    .stub(SearchService.prototype, "getAppProvidedEngines")
    .resolves(searchEngines);
  sandbox.stub(NewTabUtils.pinnedLinks, "pin").callsFake((site, index) => {
    NewTabUtils.pinnedLinks.links[index] = site;
  });

  let prepTopSites = () => {
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
      true
    );
    Services.prefs.setStringPref(
      "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.searchEngines",
      "google,amazon"
    );
    Services.prefs.setStringPref(
      "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.havePinned",
      ""
    );
  };

  {
    info(
      "TopSites should updateCustomSearchShortcuts when experiment " +
        "pref is turned on"
    );
    let cleanup = stubTopSites(sandbox);
    prepTopSites();
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
      false
    );
    // stubTopSites stubs updateCustomSearchShortcuts, when we need to add
    // a spy.
    TopSites.updateCustomSearchShortcuts.restore();
    sandbox.spy(TopSites, "updateCustomSearchShortcuts");

    // turn the experiment on
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
      true
    );

    Assert.ok(
      TopSites.updateCustomSearchShortcuts.calledOnce,
      "TopSites.updateCustomSearchShortcuts called once"
    );
    Services.prefs.clearUserPref(
      "browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts"
    );
    TopSites.updateCustomSearchShortcuts.restore();
    await cleanup();
  }

  {
    info(
      "TopSites should filter out default top sites that match a " +
        "hostname of a search shortcut if previously blocked"
    );
    let cleanup = stubTopSites(sandbox);
    prepTopSites();
    TopSites.refreshDefaults("https://amazon.ca");
    sandbox
      .stub(NewTabUtils.blockedLinks, "links")
      .value([{ url: "https://amazon.com" }]);
    sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => {
      return NewTabUtils.blockedLinks.links[0].url === site.url;
    });

    let urlsReturned = (await TopSites.getLinksWithDefaults()).map(
      link => link.url
    );
    Assert.ok(!urlsReturned.includes("https://amazon.ca"));
    await cleanup();
  }

  {
    info("TopSites should update frecent search topsite icon");
    let cleanup = stubTopSites(sandbox);
    prepTopSites();
    sandbox.stub(TopSites._tippyTopProvider, "processSite").callsFake(site => {
      site.tippyTopIcon = "icon.png";
      site.backgroundColor = "#fff";
      return site;
    });
    gGetTopSitesStub.resolves([{ url: "https://google.com" }]);

    let urlsReturned = await TopSites.getLinksWithDefaults();

    let defaultSearchTopsite = urlsReturned.find(
      s => s.url === "https://google.com"
    );
    Assert.ok(defaultSearchTopsite.searchTopSite);
    Assert.equal(defaultSearchTopsite.tippyTopIcon, "icon.png");
    Assert.equal(defaultSearchTopsite.backgroundColor, "#fff");
    gGetTopSitesStub.resolves(FAKE_LINKS);
    TopSites._tippyTopProvider.processSite.restore();
    await cleanup();
  }

  {
    info("TopSites should update default search topsite icon");
    let cleanup = stubTopSites(sandbox);
    prepTopSites();
    sandbox.stub(TopSites._tippyTopProvider, "processSite").callsFake(site => {
      site.tippyTopIcon = "icon.png";
      site.backgroundColor = "#fff";
      return site;
    });
    gGetTopSitesStub.resolves([{ url: "https://foo.com" }]);

    let urlsReturned = await TopSites.getLinksWithDefaults();

    let defaultSearchTopsite = urlsReturned.find(
      s => s.url === "https://amazon.com"
    );
    Assert.ok(defaultSearchTopsite.searchTopSite);
    Assert.equal(defaultSearchTopsite.tippyTopIcon, "icon.png");
    Assert.equal(defaultSearchTopsite.backgroundColor, "#fff");
    gGetTopSitesStub.resolves(FAKE_LINKS);
    TopSites._tippyTopProvider.processSite.restore();
    await cleanup();
  }

  {
    info(
      "TopSites should dispatch UPDATE_SEARCH_SHORTCUTS on " +
        "updateCustomSearchShortcuts"
    );
    let cleanup = stubTopSites(sandbox);
    prepTopSites();
    Services.prefs.setBoolPref(
      "browser.newtabpage.activity-stream.improvesearch.noDefaultSearchTile",
      true
    );
    // stubTopSites stubs updateCustomSearchShortcuts, when in this case, we
    // want to check the effect of the method.
    TopSites.updateCustomSearchShortcuts.restore();
    await TopSites.updateCustomSearchShortcuts();
    let searchShortcuts = await TopSites.getSearchShortcuts();
    Assert.deepEqual(searchShortcuts, [
      {
        keyword: "@google",
        shortURL: "google",
        url: "https://google.com",
        backgroundColor: undefined,
        smallFavicon:
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=95 H=95 G=94

¤ Dauer der Verarbeitung: 0.24 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.