Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { TopSites, DEFAULT_TOP_SITES } = ChromeUtils.importESModule(
"resource:///modules/TopSites.sys.mjs"
);
const { actionTypes: at } = ChromeUtils.importESModule(
"resource://activity-stream/common/Actions.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
FilterAdult: "resource://activity-stream/lib/FilterAdult.sys.mjs",
NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
shortURL: "resource://activity-stream/lib/ShortURL.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
Screenshots: "resource://activity-stream/lib/Screenshots.sys.mjs",
SearchService: "resource://gre/modules/SearchService.sys.mjs",
TOP_SITES_DEFAULT_ROWS: "resource://activity-stream/common/Reducers.sys.mjs",
TOP_SITES_MAX_SITES_PER_ROW:
"resource://activity-stream/common/Reducers.sys.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,
}));
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",
);
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");
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");
Assert.equal(
DEFAULT_TOP_SITES.length,
1,
"Should have a DEFAULT_TOP_SITES now."
);
let [site] = DEFAULT_TOP_SITES;
Assert.equal(
site.hostname,
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("");
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);
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);
info("getLinksWithDefaults should get the links from NewTabUtils");
let result = await TopSites.getLinksWithDefaults();
const reference = FAKE_LINKS.map(site =>
Object.assign({}, site, {
hostname: 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([]);
sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [{ url: TEST_URL }]);
let cleanup = stubTopSites(sandbox);
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: shortURL({ url }),
typedBonus: true,
url,
};
gGetTopSitesStub.resolves([topsite]);
sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => {
return site.url === blockedDefaultSite.url;
});
let cleanup = stubTopSites(sandbox);
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);
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);
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);
let result = await TopSites.getLinksWithDefaults();
let reference = [...TEST_LINKS, ...DEFAULT_TOP_SITES].map(s =>
Object.assign({}, s, {
hostname: 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);
let result = await TopSites.getLinksWithDefaults();
let reference = [...testLinks, DEFAULT_TOP_SITES[0]].map(s =>
Object.assign({}, s, {
hostname: 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);
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,
}));
gGetTopSitesStub.resolves(testLinks);
let cleanup = stubTopSites(sandbox);
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);
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);
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);
gGetTopSitesStub.resetHistory();
sandbox
.stub(NewTabUtils.pinnedLinks, "links")
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);
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);
const TEST_SCREENSHOT = "screenshot";
gGetTopSitesStub.resolves([
]);
sandbox
.stub(NewTabUtils.pinnedLinks, "links")
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);
gGetTopSitesStub.resolves([
]);
sandbox
.stub(NewTabUtils.pinnedLinks, "links")
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);
const CUSTOM_SCREENSHOT = "custom";
gGetTopSitesStub.resolves([]);
sandbox
.stub(NewTabUtils.pinnedLinks, "links")
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);
const EXISTING_SCREENSHOT = "some-screenshot";
sandbox
.stub(NewTabUtils.pinnedLinks, "links")
.get(() => [
]);
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);
const EXISTING_SCREENSHOT = "some-screenshot";
gGetTopSitesStub.resolves([
{
searchTopSite: true,
screenshot: EXISTING_SCREENSHOT,
},
]);
sandbox
.stub(NewTabUtils.pinnedLinks, "links")
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);
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);
sandbox
.stub(NewTabUtils.pinnedLinks, "links")
.get(() => [
]);
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(() => [
]);
gGetTopSitesStub.resolves([
{ 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")
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: 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: 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"
);
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({
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(() => [null, null, { 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(() => [null, null, 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(() => [null, null, { 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")
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();
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 = [
];
gGetTopSitesStub.resolves([
...top5Test.map(url => ({ url })),
]);
const urlsReturned = (await TopSites.getLinksWithDefaults()).map(
link => link.url
);
Assert.ok(
"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([
]);
let urlsReturned = (await TopSites.getLinksWithDefaults()).map(
link => link.url
);
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",
);
let urlsReturned = (await TopSites.getLinksWithDefaults()).map(
link => link.url
);
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" }]);
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();
sandbox
.stub(NewTabUtils.blockedLinks, "links")
sandbox.stub(NewTabUtils.blockedLinks, "isBlocked").callsFake(site => {
return NewTabUtils.blockedLinks.links[0].url === site.url;
});
let urlsReturned = (await TopSites.getLinksWithDefaults()).map(
link => link.url
);
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;
});
let urlsReturned = await TopSites.getLinksWithDefaults();
let defaultSearchTopsite = urlsReturned.find(
);
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;
});
let urlsReturned = await TopSites.getLinksWithDefaults();
let defaultSearchTopsite = urlsReturned.find(
);
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",
backgroundColor: undefined,
smallFavicon:
tippyTopIcon:
},
{
keyword: "@amazon",
shortURL: "amazon",
backgroundColor: undefined,
smallFavicon:
tippyTopIcon:
},
]);
await cleanup();
}
{
info(
"TopSites should refresh when top sites search shortcut feature gate pref changes."
);
let cleanup = stubTopSites(sandbox);
prepTopSites();
let promise = TestUtils.topicObserved("topsites-refreshed");
Services.prefs.setBoolPref(
"browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
false
);
await promise;
let sites = await TopSites.getSites();
let searchTopSiteCount = sites.reduce(
(acc, current) => (current.searchTopSite ? 1 : 0 + acc),
0
);
Assert.equal(searchTopSiteCount, 0, "Number of search top sites.");
promise = TestUtils.topicObserved("topsites-refreshed");
Services.prefs.setBoolPref(
"browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts",
true
);
await promise;
sites = await TopSites.getSites();
searchTopSiteCount = sites.reduce(
(acc, current) => acc + (current.searchTopSite ? 1 : 0),
0
);
Assert.equal(searchTopSiteCount, 2, "Number of search top sites.");
await cleanup();
}
Services.prefs.clearUserPref(
"browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts"
);
Services.prefs.clearUserPref(
"browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.searchEngines"
);
Services.prefs.clearUserPref(
"browser.newtabpage.activity-stream.improvesearch.topSiteSearchShortcuts.havePinned"
);
sandbox.restore();
});
add_task(async function test_updatePinnedSearchShortcuts() {
let sandbox = sinon.createSandbox();
sandbox.stub(NewTabUtils.pinnedLinks, "pin");
sandbox.stub(NewTabUtils.pinnedLinks, "unpin");
{
info(
"TopSites.updatePinnedSearchShortcuts should unpin a " +
"shortcut in deletedShortcuts"
);
let cleanup = stubTopSites(sandbox);
let deletedShortcuts = [
{
searchVendor: "google",
label: "google",
searchTopSite: true,
},
];
let addedShortcuts = [];
sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [
null,
null,
{
searchVendor: "amazon",
label: "amazon",
searchTopSite: true,
},
]);
TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts });
Assert.ok(
NewTabUtils.pinnedLinks.pin.notCalled,
"NewTabUtils.pinnedLinks.pin not called"
);
Assert.ok(
NewTabUtils.pinnedLinks.unpin.calledOnce,
"NewTabUtils.pinnedLinks.unpin called once"
);
Assert.ok(
NewTabUtils.pinnedLinks.unpin.calledWith({
})
);
NewTabUtils.pinnedLinks.pin.resetHistory();
NewTabUtils.pinnedLinks.unpin.resetHistory();
await cleanup();
}
{
info(
"TopSites.updatePinnedSearchShortcuts should pin a shortcut " +
"in addedShortcuts"
);
let cleanup = stubTopSites(sandbox);
let addedShortcuts = [
{
searchVendor: "google",
label: "google",
searchTopSite: true,
},
];
let deletedShortcuts = [];
sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [
null,
null,
{
searchVendor: "amazon",
label: "amazon",
searchTopSite: true,
},
]);
TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts });
Assert.ok(
NewTabUtils.pinnedLinks.unpin.notCalled,
"NewTabUtils.pinnedLinks.unpin not called"
);
Assert.ok(
NewTabUtils.pinnedLinks.pin.calledOnce,
"NewTabUtils.pinnedLinks.pin called once"
);
Assert.ok(
NewTabUtils.pinnedLinks.pin.calledWith(
{
label: "google",
searchTopSite: true,
searchVendor: "google",
},
0
)
);
NewTabUtils.pinnedLinks.pin.resetHistory();
NewTabUtils.pinnedLinks.unpin.resetHistory();
await cleanup();
}
{
info(
"TopSites.updatePinnedSearchShortcuts should pin and unpin " +
"in the same action"
);
let cleanup = stubTopSites(sandbox);
let addedShortcuts = [
{
searchVendor: "google",
label: "google",
searchTopSite: true,
},
{
searchVendor: "ebay",
label: "ebay",
searchTopSite: true,
},
];
let deletedShortcuts = [
{
searchVendor: "amazon",
label: "amazon",
searchTopSite: true,
},
];
sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => [
{
searchVendor: "amazon",
label: "amazon",
searchTopSite: true,
},
]);
TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts });
Assert.ok(
NewTabUtils.pinnedLinks.unpin.calledOnce,
"NewTabUtils.pinnedLinks.unpin called once"
);
Assert.ok(
NewTabUtils.pinnedLinks.pin.calledTwice,
"NewTabUtils.pinnedLinks.pin called twice"
);
NewTabUtils.pinnedLinks.pin.resetHistory();
NewTabUtils.pinnedLinks.unpin.resetHistory();
await cleanup();
}
{
info(
"TopSites.updatePinnedSearchShortcuts should pin a shortcut in " +
"addedShortcuts even if pinnedLinks is full"
);
let cleanup = stubTopSites(sandbox);
let addedShortcuts = [
{
searchVendor: "google",
label: "google",
searchTopSite: true,
},
];
let deletedShortcuts = [];
sandbox.stub(NewTabUtils.pinnedLinks, "links").get(() => FAKE_LINKS);
TopSites.updatePinnedSearchShortcuts({ addedShortcuts, deletedShortcuts });
Assert.ok(
NewTabUtils.pinnedLinks.unpin.notCalled,
"NewTabUtils.pinnedLinks.unpin not called"
);
Assert.ok(
NewTabUtils.pinnedLinks.pin.calledWith(
0
),
"NewTabUtils.pinnedLinks.unpin not called"
);
NewTabUtils.pinnedLinks.pin.resetHistory();
NewTabUtils.pinnedLinks.unpin.resetHistory();
await cleanup();
}
sandbox.restore();
});