Fix Bug 1449223 - Add Telemetry for failed IndexedDB transactions
This commit is contained in:
Родитель
e87a190e15
Коммит
50fa46b446
|
@ -26,7 +26,6 @@ const {FaviconFeed} = ChromeUtils.import("resource://activity-stream/lib/Favicon
|
|||
const {TopSitesFeed} = ChromeUtils.import("resource://activity-stream/lib/TopSitesFeed.jsm", {});
|
||||
const {TopStoriesFeed} = ChromeUtils.import("resource://activity-stream/lib/TopStoriesFeed.jsm", {});
|
||||
const {HighlightsFeed} = ChromeUtils.import("resource://activity-stream/lib/HighlightsFeed.jsm", {});
|
||||
const {ActivityStreamStorage} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
const {ThemeFeed} = ChromeUtils.import("resource://activity-stream/lib/ThemeFeed.jsm", {});
|
||||
const {MessageCenterFeed} = ChromeUtils.import("resource://activity-stream/lib/MessageCenterFeed.jsm", {});
|
||||
|
||||
|
@ -278,7 +277,6 @@ this.ActivityStream = class ActivityStream {
|
|||
this.store = new Store();
|
||||
this.feeds = FEEDS_CONFIG;
|
||||
this._defaultPrefs = new DefaultPrefs(PREFS_CONFIG);
|
||||
this._storage = new ActivityStreamStorage(["sectionPrefs", "snippets"]);
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -286,11 +284,6 @@ this.ActivityStream = class ActivityStream {
|
|||
this._updateDynamicPrefs();
|
||||
this._defaultPrefs.init();
|
||||
|
||||
// Accessing the db causes the object stores to be created / migrated.
|
||||
// This needs to happen before other instances try to access the db, which
|
||||
// would update only a subset of the stores to the latest version.
|
||||
this._storage.db; // eslint-disable-line no-unused-expressions
|
||||
|
||||
// Hook up the store and let all feeds and pages initialize
|
||||
this.store.init(this.feeds, ac.BroadcastToContent({
|
||||
type: at.INIT,
|
||||
|
|
|
@ -2,50 +2,82 @@ ChromeUtils.defineModuleGetter(this, "IndexedDB", "resource://gre/modules/Indexe
|
|||
|
||||
this.ActivityStreamStorage = class ActivityStreamStorage {
|
||||
/**
|
||||
* @param storeName String with the store name to access or array of strings
|
||||
* to create all the required stores
|
||||
* @param storeNames Array of strings used to create all the required stores
|
||||
*/
|
||||
constructor(storeName) {
|
||||
constructor(options = {}) {
|
||||
if (!options.storeNames || !options.telemetry) {
|
||||
throw new Error(`storeNames and telemetry are required, called only with ${Object.keys(options)}`);
|
||||
}
|
||||
|
||||
this.dbName = "ActivityStream";
|
||||
this.dbVersion = 3;
|
||||
this.storeName = storeName;
|
||||
this.storeNames = options.storeNames;
|
||||
this.telemetry = options.telemetry;
|
||||
}
|
||||
|
||||
get db() {
|
||||
return this._db || (this._db = this._openDatabase());
|
||||
}
|
||||
|
||||
async getStore() {
|
||||
return (await this.db).objectStore(this.storeName, "readwrite");
|
||||
/**
|
||||
* Public method that binds the store required by the consumer and exposes
|
||||
* the private db getters and setters.
|
||||
*
|
||||
* @param storeName String name of desired store
|
||||
*/
|
||||
getDbTable(storeName) {
|
||||
if (this.storeNames.includes(storeName)) {
|
||||
return {
|
||||
get: this._get.bind(this, storeName),
|
||||
getAll: this._getAll.bind(this, storeName),
|
||||
set: this._set.bind(this, storeName)
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Store name ${storeName} does not exist.`);
|
||||
}
|
||||
|
||||
async get(key) {
|
||||
return (await this.getStore()).get(key);
|
||||
async _getStore(storeName) {
|
||||
return (await this.db).objectStore(storeName, "readwrite");
|
||||
}
|
||||
|
||||
async getAll() {
|
||||
return (await this.getStore()).getAll();
|
||||
_get(storeName, key) {
|
||||
return this._requestWrapper(async () => (await this._getStore(storeName)).get(key));
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
return (await this.getStore()).put(value, key);
|
||||
_getAll(storeName) {
|
||||
return this._requestWrapper(async () => (await this._getStore(storeName)).getAll());
|
||||
}
|
||||
|
||||
_set(storeName, key, value) {
|
||||
return this._requestWrapper(async () => (await this._getStore(storeName)).put(value, key));
|
||||
}
|
||||
|
||||
_openDatabase() {
|
||||
return IndexedDB.open(this.dbName, {version: this.dbVersion}, db => {
|
||||
// If provided with array of objectStore names we need to create all the
|
||||
// individual stores
|
||||
if (Array.isArray(this.storeName)) {
|
||||
this.storeName.forEach(store => {
|
||||
if (!db.objectStoreNames.contains(store)) {
|
||||
db.createObjectStore(store);
|
||||
}
|
||||
});
|
||||
} else if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
db.createObjectStore(this.storeName);
|
||||
}
|
||||
this.storeNames.forEach(store => {
|
||||
if (!db.objectStoreNames.contains(store)) {
|
||||
this._requestWrapper(() => db.createObjectStore(store));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _requestWrapper(request) {
|
||||
let result = null;
|
||||
try {
|
||||
result = await request();
|
||||
} catch (e) {
|
||||
if (this.telemetry) {
|
||||
this.telemetry.handleUndesiredEvent({data: {event: "TRANSACTION_FAILED"}});
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
function getDefaultOptions(options) {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const {ActivityStreamStorage} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
const {actionCreators: ac, actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {Prefs} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
|
||||
const {PrerenderData} = ChromeUtils.import("resource://activity-stream/common/PrerenderData.jsm", {});
|
||||
|
@ -32,7 +31,6 @@ this.PrefsFeed = class PrefsFeed {
|
|||
constructor(prefMap) {
|
||||
this._prefMap = prefMap;
|
||||
this._prefs = new Prefs();
|
||||
this._storage = new ActivityStreamStorage("sectionPrefs");
|
||||
}
|
||||
|
||||
// If any prefs or the theme are set to something other than what the
|
||||
|
@ -92,6 +90,7 @@ this.PrefsFeed = class PrefsFeed {
|
|||
|
||||
init() {
|
||||
this._prefs.observeBranch(this);
|
||||
this._storage = this.store.dbStorage.getDbTable("sectionPrefs");
|
||||
|
||||
// Get the initial value of each activity stream pref
|
||||
const values = {};
|
||||
|
@ -118,8 +117,12 @@ this.PrefsFeed = class PrefsFeed {
|
|||
|
||||
async _setIndexedDBPref(id, value) {
|
||||
const name = id === "topsites" ? id : `feeds.section.${id}`;
|
||||
await this._storage.set(name, value);
|
||||
this._setPrerenderPref();
|
||||
try {
|
||||
await this._storage.set(name, value);
|
||||
this._setPrerenderPref();
|
||||
} catch (e) {
|
||||
Cu.reportError("Could not set section preferences.");
|
||||
}
|
||||
}
|
||||
|
||||
onAction(action) {
|
||||
|
|
|
@ -7,7 +7,7 @@ ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
|
|||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const {actionCreators: ac, actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {ActivityStreamStorage, getDefaultOptions} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
const {getDefaultOptions} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
|
||||
|
||||
|
@ -80,8 +80,8 @@ const SectionsManager = {
|
|||
},
|
||||
initialized: false,
|
||||
sections: new Map(),
|
||||
async init(prefs = {}) {
|
||||
this._storage = new ActivityStreamStorage("sectionPrefs");
|
||||
async init(prefs = {}, storage) {
|
||||
this._storage = storage;
|
||||
|
||||
for (const feedPrefName of Object.keys(BUILT_IN_SECTIONS)) {
|
||||
const optionsPrefName = `${feedPrefName}.options`;
|
||||
|
@ -126,13 +126,19 @@ const SectionsManager = {
|
|||
},
|
||||
async addBuiltInSection(feedPrefName, optionsPrefValue = "{}") {
|
||||
let options;
|
||||
let storedPrefs;
|
||||
try {
|
||||
options = JSON.parse(optionsPrefValue);
|
||||
} catch (e) {
|
||||
options = {};
|
||||
Cu.reportError(`Problem parsing options pref for ${feedPrefName}`);
|
||||
}
|
||||
const storedPrefs = await this._storage.get(feedPrefName) || {};
|
||||
try {
|
||||
storedPrefs = await this._storage.get(feedPrefName) || {};
|
||||
} catch (e) {
|
||||
storedPrefs = {};
|
||||
Cu.reportError(`Problem getting stored prefs for ${feedPrefName}`);
|
||||
}
|
||||
const defaultSection = BUILT_IN_SECTIONS[feedPrefName](options);
|
||||
const section = Object.assign({}, defaultSection, {pref: Object.assign({}, defaultSection.pref, getDefaultOptions(storedPrefs))});
|
||||
section.pref.feed = feedPrefName;
|
||||
|
@ -406,7 +412,7 @@ class SectionsFeed {
|
|||
break;
|
||||
// Wait for pref values, as some sections have options stored in prefs
|
||||
case at.PREFS_INITIAL_VALUES:
|
||||
SectionsManager.init(action.data);
|
||||
SectionsManager.init(action.data, this.store.dbStorage.getDbTable("sectionPrefs"));
|
||||
break;
|
||||
case at.PREF_CHANGED: {
|
||||
if (action.data) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {actionTypes: at, actionCreators: ac} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
|
||||
const {ActivityStreamStorage} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
@ -39,7 +38,6 @@ this.SnippetsFeed = class SnippetsFeed {
|
|||
this._refresh = this._refresh.bind(this);
|
||||
this._totalBookmarks = null;
|
||||
this._totalBookmarksLastUpdated = null;
|
||||
this._storage = new ActivityStreamStorage("snippets");
|
||||
}
|
||||
|
||||
get snippetsURL() {
|
||||
|
@ -169,6 +167,7 @@ this.SnippetsFeed = class SnippetsFeed {
|
|||
}
|
||||
|
||||
async init() {
|
||||
this._storage = this.store.dbStorage.getDbTable("snippets");
|
||||
Services.obs.addObserver(this, SEARCH_ENGINE_OBSERVER_TOPIC);
|
||||
this._previousSessionEnd = await this._storage.get("previousSessionEnd");
|
||||
await this._refresh();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"use strict";
|
||||
|
||||
const {ActivityStreamMessageChannel} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamMessageChannel.jsm", {});
|
||||
const {ActivityStreamStorage} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
const {Prefs} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
|
||||
const {reducers} = ChromeUtils.import("resource://activity-stream/common/Reducers.jsm", {});
|
||||
const {redux} = ChromeUtils.import("resource://activity-stream/vendor/Redux.jsm", {});
|
||||
|
@ -34,6 +35,7 @@ this.Store = class Store {
|
|||
redux.combineReducers(reducers),
|
||||
redux.applyMiddleware(this._middleware, this._messageChannel.middleware)
|
||||
);
|
||||
this.storage = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,7 +114,7 @@ this.Store = class Store {
|
|||
* to feeds when they're created.
|
||||
* @param {Action} uninitAction An optional action for when feeds uninit.
|
||||
*/
|
||||
init(feedFactories, initAction, uninitAction) {
|
||||
async init(feedFactories, initAction, uninitAction) {
|
||||
this._feedFactories = feedFactories;
|
||||
this._initAction = initAction;
|
||||
this._uninitAction = uninitAction;
|
||||
|
@ -122,6 +124,8 @@ this.Store = class Store {
|
|||
this.initFeed(telemetryKey);
|
||||
}
|
||||
|
||||
await this._initIndexedDB(telemetryKey);
|
||||
|
||||
for (const pref of feedFactories.keys()) {
|
||||
if (pref !== telemetryKey && this._prefs.get(pref)) {
|
||||
this.initFeed(pref);
|
||||
|
@ -140,6 +144,17 @@ this.Store = class Store {
|
|||
this._messageChannel.simulateMessagesForExistingTabs();
|
||||
}
|
||||
|
||||
async _initIndexedDB(telemetryKey) {
|
||||
this.dbStorage = new ActivityStreamStorage({
|
||||
storeNames: ["sectionPrefs", "snippets"],
|
||||
telemetry: this.feeds.get(telemetryKey)
|
||||
});
|
||||
// Accessing the db causes the object stores to be created / migrated.
|
||||
// This needs to happen before other instances try to access the db, which
|
||||
// would update only a subset of the stores to the latest version.
|
||||
await this.dbStorage.db; // eslint-disable-line no-unused-expressions
|
||||
}
|
||||
|
||||
/**
|
||||
* uninit - Uninitalizes each feed, clears them, and destroys the message
|
||||
* manager channel.
|
||||
|
|
|
@ -10,7 +10,7 @@ const {TippyTopProvider} = ChromeUtils.import("resource://activity-stream/lib/Ti
|
|||
const {insertPinned, TOP_SITES_MAX_SITES_PER_ROW} = ChromeUtils.import("resource://activity-stream/common/Reducers.jsm", {});
|
||||
const {Dedupe} = ChromeUtils.import("resource://activity-stream/common/Dedupe.jsm", {});
|
||||
const {shortURL} = ChromeUtils.import("resource://activity-stream/lib/ShortURL.jsm", {});
|
||||
const {ActivityStreamStorage, getDefaultOptions} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
const {getDefaultOptions} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamStorage.jsm", {});
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "filterAdult",
|
||||
"resource://activity-stream/lib/FilterAdult.jsm");
|
||||
|
@ -42,7 +42,14 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
this.pinnedCache = new LinksCache(NewTabUtils.pinnedLinks, "links",
|
||||
[...CACHED_LINK_PROPS_TO_MIGRATE, ...PINNED_FAVICON_PROPS_TO_MIGRATE]);
|
||||
PageThumbs.addExpirationFilter(this);
|
||||
this._storage = new ActivityStreamStorage("sectionPrefs");
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this._tippyTopProvider.init();
|
||||
// If the feed was previously disabled PREFS_INITIAL_VALUES was never received
|
||||
this.refreshDefaults(this.store.getState().Prefs.values[DEFAULT_SITES_PREF]);
|
||||
this._storage = this.store.dbStorage.getDbTable("sectionPrefs");
|
||||
this.refresh({broadcast: true});
|
||||
}
|
||||
|
||||
uninit() {
|
||||
|
@ -165,14 +172,15 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
* @param {bool} options.broadcast Should the update be broadcasted.
|
||||
*/
|
||||
async refresh(options = {}) {
|
||||
if (!this._tippyTopProvider.initialized) {
|
||||
await this._tippyTopProvider.init();
|
||||
}
|
||||
|
||||
const links = await this.getLinksWithDefaults();
|
||||
const newAction = {type: at.TOP_SITES_UPDATED, data: {links}};
|
||||
|
||||
const storedPrefs = await this._storage.get(SECTION_ID) || {};
|
||||
let storedPrefs;
|
||||
try {
|
||||
storedPrefs = await this._storage.get(SECTION_ID) || {};
|
||||
} catch (e) {
|
||||
storedPrefs = {};
|
||||
Cu.reportError("Problem getting stored prefs for TopSites");
|
||||
}
|
||||
newAction.data.pref = getDefaultOptions(storedPrefs);
|
||||
|
||||
if (options.broadcast) {
|
||||
|
@ -374,9 +382,7 @@ this.TopSitesFeed = class TopSitesFeed {
|
|||
onAction(action) {
|
||||
switch (action.type) {
|
||||
case at.INIT:
|
||||
// If the feed was previously disabled PREFS_INITIAL_VALUES was never received
|
||||
this.refreshDefaults(this.store.getState().Prefs.values[DEFAULT_SITES_PREF]);
|
||||
this.refresh({broadcast: true});
|
||||
this.init();
|
||||
break;
|
||||
case at.SYSTEM_TICK:
|
||||
this.refresh({broadcast: false});
|
||||
|
|
|
@ -34,7 +34,6 @@ describe("ActivityStream", () => {
|
|||
sandbox.stub(as.store, "uninit");
|
||||
sandbox.stub(as._defaultPrefs, "init");
|
||||
sandbox.stub(as._defaultPrefs, "reset");
|
||||
sandbox.stub(as._storage, "_openDatabase");
|
||||
});
|
||||
|
||||
afterEach(() => sandbox.restore());
|
||||
|
@ -58,14 +57,10 @@ describe("ActivityStream", () => {
|
|||
it("should call .store.init", () => {
|
||||
assert.calledOnce(as.store.init);
|
||||
});
|
||||
it("should cause storage to open database", () => {
|
||||
assert.calledOnce(as._storage._openDatabase);
|
||||
});
|
||||
it("should pass to Store an INIT event with the right version", () => {
|
||||
as = new ActivityStream({version: "1.2.3"});
|
||||
sandbox.stub(as.store, "init");
|
||||
sandbox.stub(as._defaultPrefs, "init");
|
||||
sandbox.stub(as._storage, "_openDatabase");
|
||||
|
||||
as.init();
|
||||
|
||||
|
|
|
@ -9,9 +9,12 @@ describe("ActivityStreamStorage", () => {
|
|||
let storage;
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
indexedDB = {open: sandbox.stub().returns(Promise.resolve({}))};
|
||||
indexedDB = {open: sandbox.stub().resolves({})};
|
||||
overrider.set({IndexedDB: indexedDB});
|
||||
storage = new ActivityStreamStorage("storage_test");
|
||||
storage = new ActivityStreamStorage({
|
||||
storeNames: ["storage_test"],
|
||||
telemetry: {handleUndesiredEvent: sandbox.stub()}
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
|
@ -19,14 +22,61 @@ describe("ActivityStreamStorage", () => {
|
|||
it("should not throw an error when accessing db", async () => {
|
||||
assert.ok(storage.db);
|
||||
});
|
||||
it("should revert key value parameters for put", async () => {
|
||||
const stub = sandbox.stub();
|
||||
sandbox.stub(storage, "getStore").resolves({put: stub});
|
||||
it("should throw if arguments not provided", () => {
|
||||
assert.throws(() => new ActivityStreamStorage());
|
||||
});
|
||||
describe("#getDbTable", () => {
|
||||
let testStorage;
|
||||
let storeStub;
|
||||
beforeEach(() => {
|
||||
storeStub = {
|
||||
getAll: sandbox.stub().resolves(),
|
||||
get: sandbox.stub().resolves(),
|
||||
put: sandbox.stub().resolves()
|
||||
};
|
||||
sandbox.stub(storage, "_getStore").resolves(storeStub);
|
||||
testStorage = storage.getDbTable("storage_test");
|
||||
});
|
||||
it("should reverse key value parameters for put", async () => {
|
||||
await testStorage.set("key", "value");
|
||||
|
||||
await storage.set("key", "value");
|
||||
assert.calledOnce(storeStub.put);
|
||||
assert.calledWith(storeStub.put, "value", "key");
|
||||
});
|
||||
it("should return the correct value for get", async () => {
|
||||
storeStub.get.withArgs("foo").resolves("foo");
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWith(stub, "value", "key");
|
||||
const result = await testStorage.get("foo");
|
||||
|
||||
assert.calledOnce(storeStub.get);
|
||||
assert.equal(result, "foo");
|
||||
});
|
||||
it("should return the correct value for getAll", async () => {
|
||||
storeStub.getAll.resolves(["bar"]);
|
||||
|
||||
const result = await testStorage.getAll();
|
||||
|
||||
assert.calledOnce(storeStub.getAll);
|
||||
assert.deepEqual(result, ["bar"]);
|
||||
});
|
||||
it("should query the correct object store", async () => {
|
||||
await testStorage.get();
|
||||
|
||||
assert.calledOnce(storage._getStore);
|
||||
assert.calledWithExactly(storage._getStore, "storage_test");
|
||||
});
|
||||
it("should throw if table is not found", () => {
|
||||
assert.throws(() => storage.getDbTable("undefined_store"));
|
||||
});
|
||||
});
|
||||
it("should get the correct objectStore when calling _getStore", async () => {
|
||||
const objectStoreStub = sandbox.stub();
|
||||
indexedDB.open.resolves({objectStore: objectStoreStub});
|
||||
|
||||
await storage._getStore("foo");
|
||||
|
||||
assert.calledOnce(objectStoreStub);
|
||||
assert.calledWithExactly(objectStoreStub, "foo", "readwrite");
|
||||
});
|
||||
it("should create a db with the correct store name", async () => {
|
||||
const dbStub = {createObjectStore: sandbox.stub(), objectStoreNames: {contains: sandbox.stub().returns(false)}};
|
||||
|
@ -39,7 +89,10 @@ describe("ActivityStreamStorage", () => {
|
|||
assert.calledWithExactly(dbStub.createObjectStore, "storage_test");
|
||||
});
|
||||
it("should handle an array of object store names", async () => {
|
||||
storage = new ActivityStreamStorage(["store1", "store2"]);
|
||||
storage = new ActivityStreamStorage({
|
||||
storeNames: ["store1", "store2"],
|
||||
telemetry: {}
|
||||
});
|
||||
const dbStub = {createObjectStore: sandbox.stub(), objectStoreNames: {contains: sandbox.stub().returns(false)}};
|
||||
await storage.db;
|
||||
|
||||
|
@ -51,7 +104,10 @@ describe("ActivityStreamStorage", () => {
|
|||
assert.calledWith(dbStub.createObjectStore, "store2");
|
||||
});
|
||||
it("should skip creating existing stores", async () => {
|
||||
storage = new ActivityStreamStorage(["store1", "store2"]);
|
||||
storage = new ActivityStreamStorage({
|
||||
storeNames: ["store1", "store2"],
|
||||
telemetry: {}
|
||||
});
|
||||
const dbStub = {createObjectStore: sandbox.stub(), objectStoreNames: {contains: sandbox.stub().returns(true)}};
|
||||
await storage.db;
|
||||
|
||||
|
@ -60,4 +116,19 @@ describe("ActivityStreamStorage", () => {
|
|||
|
||||
assert.notCalled(dbStub.createObjectStore);
|
||||
});
|
||||
describe("#_requestWrapper", () => {
|
||||
it("should return a successful result", async () => {
|
||||
const result = await storage._requestWrapper(() => Promise.resolve("foo"));
|
||||
|
||||
assert.equal(result, "foo");
|
||||
assert.notCalled(storage.telemetry.handleUndesiredEvent);
|
||||
});
|
||||
it("should report failures", async () => {
|
||||
try {
|
||||
await storage._requestWrapper(() => Promise.reject(new Error()));
|
||||
} catch (e) {
|
||||
assert.calledOnce(storage.telemetry.handleUndesiredEvent);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,11 +17,18 @@ describe("PrefsFeed", () => {
|
|||
sandbox = sinon.sandbox.create();
|
||||
FAKE_PREFS = new Map([["foo", 1], ["bar", 2]]);
|
||||
feed = new PrefsFeed(FAKE_PREFS);
|
||||
const storage = {
|
||||
getAll: sandbox.stub().resolves(),
|
||||
set: sandbox.stub().resolves()
|
||||
};
|
||||
feed.store = {
|
||||
dispatch: sinon.spy(),
|
||||
getState() { return this.state; },
|
||||
state: {Theme: {className: ""}}
|
||||
state: {Theme: {className: ""}},
|
||||
dbStorage: {getDbTable: sandbox.stub().returns(storage)}
|
||||
};
|
||||
// Setup for tests that don't call `init`
|
||||
feed._storage = storage;
|
||||
feed._prefs = {
|
||||
get: sinon.spy(item => FAKE_PREFS.get(item)),
|
||||
set: sinon.spy((name, value) => FAKE_PREFS.set(name, value)),
|
||||
|
@ -31,17 +38,7 @@ describe("PrefsFeed", () => {
|
|||
ignoreBranch: sinon.spy(),
|
||||
reset: sinon.stub()
|
||||
};
|
||||
const fakeDB = {
|
||||
objectStore: sandbox.stub().returns({
|
||||
get: sandbox.stub().returns(Promise.resolve()),
|
||||
set: sandbox.stub().returns(Promise.resolve())
|
||||
})
|
||||
};
|
||||
overrider.set({
|
||||
PrivateBrowsingUtils: {enabled: true},
|
||||
ActivityStreamStorage: function Fake() {},
|
||||
IndexedDB: {open: () => Promise.resolve(fakeDB)}
|
||||
});
|
||||
overrider.set({PrivateBrowsingUtils: {enabled: true}});
|
||||
});
|
||||
afterEach(() => {
|
||||
overrider.restore();
|
||||
|
@ -65,6 +62,12 @@ describe("PrefsFeed", () => {
|
|||
assert.calledOnce(feed._prefs.observeBranch);
|
||||
assert.calledWith(feed._prefs.observeBranch, feed);
|
||||
});
|
||||
it("should initialise the storage on init", () => {
|
||||
feed.init();
|
||||
|
||||
assert.calledOnce(feed.store.dbStorage.getDbTable);
|
||||
assert.calledWithExactly(feed.store.dbStorage.getDbTable, "sectionPrefs");
|
||||
});
|
||||
it("should remove the branch observer on uninit", () => {
|
||||
feed.onAction({type: at.UNINIT});
|
||||
assert.calledOnce(feed._prefs.ignoreBranch);
|
||||
|
@ -84,7 +87,6 @@ describe("PrefsFeed", () => {
|
|||
});
|
||||
it("should set prerender pref to true if prefs match initial values", async () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([]));
|
||||
|
||||
await feed._setPrerenderPref();
|
||||
|
||||
|
@ -93,7 +95,6 @@ describe("PrefsFeed", () => {
|
|||
it("should set prerender pref to false if a pref does not match its initial value", async () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
FAKE_PREFS.set("showSearch", false);
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([]));
|
||||
|
||||
await feed._setPrerenderPref();
|
||||
|
||||
|
@ -101,7 +102,7 @@ describe("PrefsFeed", () => {
|
|||
});
|
||||
it("should set prerender pref to true if indexedDB prefs are unchanged", async () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([{collapsed: false}, {collapsed: false}]));
|
||||
feed._storage.getAll.resolves([{collapsed: false}, {collapsed: false}]);
|
||||
|
||||
await feed._setPrerenderPref();
|
||||
|
||||
|
@ -110,7 +111,7 @@ describe("PrefsFeed", () => {
|
|||
it("should set prerender pref to false if a indexedDB pref changed value", async () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
FAKE_PREFS.set("showSearch", false);
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([{collapsed: false}, {collapsed: true}]));
|
||||
feed._storage.getAll.resolves([{collapsed: false}, {collapsed: true}]);
|
||||
|
||||
await feed._setPrerenderPref();
|
||||
|
||||
|
@ -170,7 +171,6 @@ describe("PrefsFeed", () => {
|
|||
assert.calledOnce(feed._setIndexedDBPref);
|
||||
});
|
||||
it("should store the pref value", async () => {
|
||||
sandbox.stub(feed._storage, "set").returns(Promise.resolve());
|
||||
sandbox.stub(feed, "_setPrerenderPref");
|
||||
await feed._setIndexedDBPref("topsites", "foo");
|
||||
|
||||
|
@ -178,12 +178,20 @@ describe("PrefsFeed", () => {
|
|||
assert.calledWith(feed._storage.set, "topsites", "foo");
|
||||
});
|
||||
it("should call _setPrerenderPref", async () => {
|
||||
sandbox.stub(feed._storage, "set").returns(Promise.resolve());
|
||||
sandbox.stub(feed, "_setPrerenderPref");
|
||||
await feed._setIndexedDBPref("topsites", "foo");
|
||||
|
||||
assert.calledOnce(feed._setPrerenderPref);
|
||||
});
|
||||
it("should catch any save errors", () => {
|
||||
const globals = new GlobalOverrider();
|
||||
globals.sandbox.spy(global.Cu, "reportError");
|
||||
feed._storage.set.throws(new Error());
|
||||
|
||||
assert.doesNotThrow(() => feed._setIndexedDBPref());
|
||||
assert.calledOnce(Cu.reportError);
|
||||
globals.restore();
|
||||
});
|
||||
});
|
||||
describe("onPrefChanged prerendering", () => {
|
||||
it("should not change the prerender pref if the pref is not included in invalidatingPrefs", () => {
|
||||
|
@ -212,7 +220,6 @@ describe("PrefsFeed", () => {
|
|||
FAKE_PREFS.set("showSearch", false);
|
||||
feed._prefs.set("showSearch", true);
|
||||
feed.onPrefChanged("showSearch", true);
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([]));
|
||||
|
||||
await feed._setPrerenderPref();
|
||||
|
||||
|
@ -223,7 +230,6 @@ describe("PrefsFeed", () => {
|
|||
FAKE_PREFS.set("showSearch", false);
|
||||
feed._prefs.set("showSearch", false);
|
||||
feed.onPrefChanged("showSearch", false);
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([]));
|
||||
|
||||
await feed._setPrerenderPref();
|
||||
|
||||
|
@ -266,16 +272,14 @@ describe("PrefsFeed", () => {
|
|||
|
||||
assert.calledOnce(feed._setPrerenderPref);
|
||||
});
|
||||
it("should should set the prerender pref to false if the theme is changed to be different than the default", async () => {
|
||||
it("should set the prerender pref to false if the theme is changed to be different than the default", async () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([]));
|
||||
await feed._setPrerenderPref({className: "dark-theme"});
|
||||
// feed.onAction({type: at.THEME_UPDATE, data: {className: "dark-theme"}});
|
||||
assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, false);
|
||||
});
|
||||
it("should should set the prerender pref back to true if the theme is changed to the default", async () => {
|
||||
it("should set the prerender pref back to true if the theme is changed to the default", async () => {
|
||||
Object.keys(initialPrefs).forEach(name => FAKE_PREFS.set(name, initialPrefs[name]));
|
||||
sandbox.stub(feed._storage, "getAll").returns(Promise.resolve([]));
|
||||
feed.store.state.Theme.className = "dark-theme";
|
||||
await feed._setPrerenderPref({className: ""});
|
||||
assert.calledWith(feed._prefs.set, PRERENDER_PREF_NAME, true);
|
||||
|
|
|
@ -15,29 +15,26 @@ describe("SectionsManager", () => {
|
|||
let fakeServices;
|
||||
let fakePlacesUtils;
|
||||
let sandbox;
|
||||
let storage;
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
globals = new GlobalOverrider();
|
||||
fakeServices = {prefs: {getBoolPref: sandbox.stub(), addObserver: sandbox.stub(), removeObserver: sandbox.stub()}};
|
||||
fakePlacesUtils = {history: {update: sinon.stub(), insert: sinon.stub()}};
|
||||
const fakeDB = {
|
||||
objectStore: sandbox.stub().returns({
|
||||
get: sandbox.stub().returns(Promise.resolve()),
|
||||
set: sandbox.stub().returns(Promise.resolve())
|
||||
})
|
||||
};
|
||||
globals.set({
|
||||
Services: fakeServices,
|
||||
PlacesUtils: fakePlacesUtils,
|
||||
ActivityStreamStorage: function Fake() {},
|
||||
IndexedDB: {open: () => Promise.resolve(fakeDB)}
|
||||
PlacesUtils: fakePlacesUtils
|
||||
});
|
||||
// Redecorate SectionsManager to remove any listeners that have been added
|
||||
EventEmitter.decorate(SectionsManager);
|
||||
storage = {
|
||||
get: sandbox.stub().resolves(),
|
||||
set: sandbox.stub().resolves()
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Redecorate SectionsManager to remove any listeners that have been added
|
||||
EventEmitter.decorate(SectionsManager);
|
||||
globals.restore();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
@ -46,7 +43,7 @@ describe("SectionsManager", () => {
|
|||
it("should initialise the sections map with the built in sections", async () => {
|
||||
SectionsManager.sections.clear();
|
||||
SectionsManager.initialized = false;
|
||||
await SectionsManager.init();
|
||||
await SectionsManager.init({}, storage);
|
||||
assert.equal(SectionsManager.sections.size, 2);
|
||||
assert.ok(SectionsManager.sections.has("topstories"));
|
||||
assert.ok(SectionsManager.sections.has("highlights"));
|
||||
|
@ -54,15 +51,20 @@ describe("SectionsManager", () => {
|
|||
it("should set .initialized to true", async () => {
|
||||
SectionsManager.sections.clear();
|
||||
SectionsManager.initialized = false;
|
||||
await SectionsManager.init();
|
||||
await SectionsManager.init({}, storage);
|
||||
assert.ok(SectionsManager.initialized);
|
||||
});
|
||||
it("should add observer for context menu prefs", async () => {
|
||||
SectionsManager.CONTEXT_MENU_PREFS = {"MENU_ITEM": "MENU_ITEM_PREF"};
|
||||
await SectionsManager.init();
|
||||
await SectionsManager.init({}, storage);
|
||||
assert.calledOnce(fakeServices.prefs.addObserver);
|
||||
assert.calledWith(fakeServices.prefs.addObserver, "MENU_ITEM_PREF", SectionsManager);
|
||||
});
|
||||
it("should save the reference to `storage` passed in", async () => {
|
||||
await SectionsManager.init({}, storage);
|
||||
|
||||
assert.equal(SectionsManager._storage, storage);
|
||||
});
|
||||
});
|
||||
describe("#uninit", () => {
|
||||
it("should remove observer for context menu prefs", () => {
|
||||
|
@ -89,6 +91,20 @@ describe("SectionsManager", () => {
|
|||
|
||||
assert.calledOnce(Cu.reportError);
|
||||
});
|
||||
it("should not throw if the indexedDB operation fails", async () => {
|
||||
globals.sandbox.spy(global.Cu, "reportError");
|
||||
storage.get.returns(new Error());
|
||||
SectionsManager._storage = storage;
|
||||
|
||||
try {
|
||||
await SectionsManager.addBuiltInSection("feeds.section.topstories");
|
||||
} catch (e) {
|
||||
assert.fail();
|
||||
}
|
||||
|
||||
assert.calledOnce(storage.get);
|
||||
assert.calledOnce(Cu.reportError);
|
||||
});
|
||||
});
|
||||
describe("#updateSectionPrefs", () => {
|
||||
it("should update the collapsed value of the section", async () => {
|
||||
|
@ -202,7 +218,7 @@ describe("SectionsManager", () => {
|
|||
|
||||
SectionsManager.updateSections = sinon.spy();
|
||||
SectionsManager.CONTEXT_MENU_PREFS = {"MENU_ITEM": "MENU_ITEM_PREF"};
|
||||
await SectionsManager.init();
|
||||
await SectionsManager.init({}, storage);
|
||||
observer.observe("", "nsPref:changed", "MENU_ITEM_PREF");
|
||||
|
||||
assert.calledOnce(SectionsManager.updateSections);
|
||||
|
@ -374,13 +390,17 @@ describe("SectionsManager", () => {
|
|||
|
||||
describe("SectionsFeed", () => {
|
||||
let feed;
|
||||
let globals = new GlobalOverrider();
|
||||
let sandbox;
|
||||
let storage;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
SectionsManager.sections.clear();
|
||||
SectionsManager.initialized = false;
|
||||
storage = {
|
||||
get: sandbox.stub().resolves(),
|
||||
set: sandbox.stub().resolves()
|
||||
};
|
||||
feed = new SectionsFeed();
|
||||
feed.store = {dispatch: sinon.spy()};
|
||||
feed.store = {
|
||||
|
@ -394,22 +414,12 @@ describe("SectionsFeed", () => {
|
|||
}
|
||||
},
|
||||
Sections: [{initialized: false}]
|
||||
}
|
||||
},
|
||||
dbStorage: {getDbTable: sandbox.stub().returns(storage)}
|
||||
};
|
||||
const fakeDB = {
|
||||
objectStore: sandbox.stub().returns({
|
||||
get: sandbox.stub().returns(Promise.resolve()),
|
||||
set: sandbox.stub().returns(Promise.resolve())
|
||||
})
|
||||
};
|
||||
globals.set({
|
||||
ActivityStreamStorage: function Fake() {},
|
||||
IndexedDB: {open: () => Promise.resolve(fakeDB)}
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
feed.uninit();
|
||||
globals.restore();
|
||||
});
|
||||
describe("#init", () => {
|
||||
it("should create a SectionsFeed", () => {
|
||||
|
@ -429,7 +439,7 @@ describe("SectionsFeed", () => {
|
|||
}
|
||||
});
|
||||
it("should call onAddSection for any already added sections in SectionsManager", async () => {
|
||||
await SectionsManager.init();
|
||||
await SectionsManager.init({}, storage);
|
||||
assert.ok(SectionsManager.sections.has("topstories"));
|
||||
assert.ok(SectionsManager.sections.has("highlights"));
|
||||
const topstories = SectionsManager.sections.get("topstories");
|
||||
|
@ -551,6 +561,8 @@ describe("SectionsFeed", () => {
|
|||
feed.onAction({type: "PREFS_INITIAL_VALUES", data: {foo: "bar"}});
|
||||
assert.calledOnce(SectionsManager.init);
|
||||
assert.calledWith(SectionsManager.init, {foo: "bar"});
|
||||
assert.calledOnce(feed.store.dbStorage.getDbTable);
|
||||
assert.calledWithExactly(feed.store.dbStorage.getDbTable, "sectionPrefs");
|
||||
});
|
||||
it("should call SectionsManager.addBuiltInSection on suitable PREF_CHANGED events", () => {
|
||||
sinon.spy(SectionsManager, "addBuiltInSection");
|
||||
|
|
|
@ -31,16 +31,9 @@ let overrider = new GlobalOverrider();
|
|||
describe("SnippetsFeed", () => {
|
||||
let sandbox;
|
||||
let clock;
|
||||
let fakeDB;
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers();
|
||||
sandbox = sinon.sandbox.create();
|
||||
fakeDB = {
|
||||
objectStore: sandbox.stub().returns({
|
||||
get: sandbox.stub().returns(Promise.resolve()),
|
||||
set: sandbox.stub().returns(Promise.resolve())
|
||||
})
|
||||
};
|
||||
overrider.set({
|
||||
ProfileAge: class ProfileAge {
|
||||
constructor() {
|
||||
|
@ -49,16 +42,7 @@ describe("SnippetsFeed", () => {
|
|||
}
|
||||
},
|
||||
FxAccounts: {config: {promiseSignUpURI: sandbox.stub().returns(Promise.resolve(signUpUrl))}},
|
||||
NewTabUtils: {activityStreamProvider: {getTotalBookmarksCount: () => Promise.resolve(42)}},
|
||||
ActivityStreamStorage: class ActivityStreamStorage {
|
||||
constructor() {
|
||||
this.init = sandbox.stub.callsFake(Promise.resolve());
|
||||
}
|
||||
init() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
IndexedDB: {open: () => Promise.resolve(fakeDB)}
|
||||
NewTabUtils: {activityStreamProvider: {getTotalBookmarksCount: () => Promise.resolve(42)}}
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
|
@ -89,19 +73,22 @@ describe("SnippetsFeed", () => {
|
|||
fullData: true
|
||||
});
|
||||
|
||||
const getStub = sandbox.stub();
|
||||
getStub.withArgs("previousSessionEnd").resolves(42);
|
||||
getStub.withArgs("blockList").resolves([1]);
|
||||
const feed = new SnippetsFeed();
|
||||
feed.store = {dispatch: sandbox.stub()};
|
||||
sandbox.stub(feed._storage, "get")
|
||||
.withArgs("previousSessionEnd")
|
||||
.resolves(42)
|
||||
.withArgs("blockList")
|
||||
.resolves([1]);
|
||||
feed.store = {
|
||||
dispatch: sandbox.stub(),
|
||||
dbStorage: {getDbTable: sandbox.stub().returns({get: getStub})}
|
||||
};
|
||||
|
||||
clock.tick(WEEK_IN_MS * 2);
|
||||
|
||||
await feed.init();
|
||||
|
||||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.calledOnce(feed.store.dbStorage.getDbTable);
|
||||
assert.calledWithExactly(feed.store.dbStorage.getDbTable, "snippets");
|
||||
|
||||
const [action] = feed.store.dispatch.firstCall.args;
|
||||
assert.propertyVal(action, "type", at.SNIPPETS_DATA);
|
||||
|
@ -132,8 +119,6 @@ describe("SnippetsFeed", () => {
|
|||
it("should call .uninit on an UNINIT action", () => {
|
||||
const feed = new SnippetsFeed();
|
||||
sandbox.stub(feed, "uninit");
|
||||
sandbox.stub(feed._storage, "set");
|
||||
feed.store = {dispatch: sandbox.stub()};
|
||||
|
||||
feed.onAction({type: at.UNINIT});
|
||||
assert.calledOnce(feed.uninit);
|
||||
|
@ -141,7 +126,7 @@ describe("SnippetsFeed", () => {
|
|||
it("should broadcast a SNIPPETS_RESET on uninit", () => {
|
||||
const feed = new SnippetsFeed();
|
||||
feed.store = {dispatch: sandbox.stub()};
|
||||
sandbox.stub(feed._storage, "set");
|
||||
feed._storage = {set: sandbox.stub()};
|
||||
|
||||
feed.uninit();
|
||||
|
||||
|
@ -149,15 +134,14 @@ describe("SnippetsFeed", () => {
|
|||
});
|
||||
it("should update the blocklist on SNIPPETS_BLOCKLIST_UPDATED", async () => {
|
||||
const feed = new SnippetsFeed();
|
||||
const saveBlockList = sandbox.stub(feed._storage, "set");
|
||||
sandbox.stub(feed._storage, "get").returns(["bar"]);
|
||||
feed.store = {dispatch: sandbox.stub()};
|
||||
feed._storage = {set: sandbox.stub(), get: sandbox.stub().returns(["bar"])};
|
||||
|
||||
await feed._saveBlockedSnippet("foo");
|
||||
|
||||
assert.calledOnce(saveBlockList);
|
||||
assert.equal(saveBlockList.args[0][0], "blockList");
|
||||
assert.deepEqual(saveBlockList.args[0][1], ["bar", "foo"]);
|
||||
assert.calledOnce(feed._storage.set);
|
||||
assert.equal(feed._storage.set.args[0][0], "blockList");
|
||||
assert.deepEqual(feed._storage.set.args[0][1], ["bar", "foo"]);
|
||||
});
|
||||
it("should broadcast a SNIPPET_BLOCKED when a SNIPPETS_BLOCKLIST_UPDATED is received", () => {
|
||||
const feed = new SnippetsFeed();
|
||||
|
@ -177,12 +161,12 @@ describe("SnippetsFeed", () => {
|
|||
});
|
||||
it("should set blockList to [] on SNIPPETS_BLOCKLIST_CLEARED", async () => {
|
||||
const feed = new SnippetsFeed();
|
||||
const stub = sandbox.stub(feed._storage, "set");
|
||||
feed._storage = {set: sandbox.stub()};
|
||||
|
||||
await feed._clearBlockList();
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWithExactly(stub, "blockList", []);
|
||||
assert.calledOnce(feed._storage.set);
|
||||
assert.calledWithExactly(feed._storage.set, "blockList", []);
|
||||
});
|
||||
it("should dispatch an update event when the Search observer is called", async () => {
|
||||
const feed = new SnippetsFeed();
|
||||
|
@ -258,12 +242,12 @@ describe("SnippetsFeed", () => {
|
|||
it("should set _previousSessionEnd on uninit", async () => {
|
||||
const feed = new SnippetsFeed();
|
||||
feed.store = {dispatch: sandbox.stub()};
|
||||
feed._storage = {set: sandbox.stub()};
|
||||
feed._previousSessionEnd = null;
|
||||
const stub = sandbox.stub(feed._storage, "set");
|
||||
|
||||
feed.uninit();
|
||||
|
||||
assert.calledOnce(stub);
|
||||
assert.calledWithExactly(stub, "previousSessionEnd", Date.now());
|
||||
assert.calledOnce(feed._storage.set);
|
||||
assert.calledWithExactly(feed._storage.set, "previousSessionEnd", Date.now());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ describe("Store", () => {
|
|||
let Store;
|
||||
let sandbox;
|
||||
let store;
|
||||
let dbStub;
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
function ActivityStreamMessageChannel(options) {
|
||||
|
@ -15,11 +16,18 @@ describe("Store", () => {
|
|||
this.middleware = sandbox.spy(s => next => action => next(action));
|
||||
this.simulateMessagesForExistingTabs = sandbox.stub();
|
||||
}
|
||||
dbStub = sandbox.stub().resolves();
|
||||
function FakeActivityStreamStorage() {
|
||||
this.db = {};
|
||||
sinon.stub(this, "db").get(dbStub);
|
||||
}
|
||||
({Store} = injector({
|
||||
"lib/ActivityStreamMessageChannel.jsm": {ActivityStreamMessageChannel},
|
||||
"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs}
|
||||
"lib/ActivityStreamPrefs.jsm": {Prefs: FakePrefs},
|
||||
"lib/ActivityStreamStorage.jsm": {ActivityStreamStorage: FakeActivityStreamStorage}
|
||||
}));
|
||||
store = new Store();
|
||||
sandbox.stub(store, "_initIndexedDB").resolves();
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
|
@ -131,35 +139,47 @@ describe("Store", () => {
|
|||
});
|
||||
});
|
||||
describe("#init", () => {
|
||||
it("should call .initFeed with each key", () => {
|
||||
it("should call .initFeed with each key", async () => {
|
||||
sinon.stub(store, "initFeed");
|
||||
store._prefs.set("foo", true);
|
||||
store._prefs.set("bar", true);
|
||||
store.init(new Map([["foo", () => {}], ["bar", () => {}]]));
|
||||
await store.init(new Map([["foo", () => {}], ["bar", () => {}]]));
|
||||
assert.calledWith(store.initFeed, "foo");
|
||||
assert.calledWith(store.initFeed, "bar");
|
||||
});
|
||||
it("should not initialize the feed if the Pref is set to false", () => {
|
||||
it("should call _initIndexedDB", async () => {
|
||||
await store.init(new Map());
|
||||
|
||||
assert.calledOnce(store._initIndexedDB);
|
||||
assert.calledWithExactly(store._initIndexedDB, "feeds.telemetry");
|
||||
});
|
||||
it("should access the db property of indexedDB", async () => {
|
||||
store._initIndexedDB.restore();
|
||||
await store.init(new Map());
|
||||
|
||||
assert.calledOnce(dbStub);
|
||||
});
|
||||
it("should not initialize the feed if the Pref is set to false", async () => {
|
||||
sinon.stub(store, "initFeed");
|
||||
store._prefs.set("foo", false);
|
||||
store.init(new Map([["foo", () => {}]]));
|
||||
await store.init(new Map([["foo", () => {}]]));
|
||||
assert.notCalled(store.initFeed);
|
||||
});
|
||||
it("should observe the pref branch", () => {
|
||||
it("should observe the pref branch", async () => {
|
||||
sinon.stub(store._prefs, "observeBranch");
|
||||
store.init(new Map());
|
||||
await store.init(new Map());
|
||||
assert.calledOnce(store._prefs.observeBranch);
|
||||
assert.calledWith(store._prefs.observeBranch, store);
|
||||
});
|
||||
it("should initialize the ActivityStreamMessageChannel channel", () => {
|
||||
store.init(new Map());
|
||||
it("should initialize the ActivityStreamMessageChannel channel", async () => {
|
||||
await store.init(new Map());
|
||||
assert.calledOnce(store._messageChannel.createChannel);
|
||||
});
|
||||
it("should emit an initial event if provided", () => {
|
||||
it("should emit an initial event if provided", async () => {
|
||||
sinon.stub(store, "dispatch");
|
||||
const action = {type: "FOO"};
|
||||
|
||||
store.init(new Map(), action);
|
||||
await store.init(new Map(), action);
|
||||
|
||||
assert.calledOnce(store.dispatch);
|
||||
assert.calledWith(store.dispatch, action);
|
||||
|
@ -175,17 +195,17 @@ describe("Store", () => {
|
|||
store.init(feedFactories);
|
||||
assert.ok(telemetrySpy.calledBefore(fooSpy));
|
||||
});
|
||||
it("should dispatch init/load events", () => {
|
||||
store.init(new Map(), {type: "FOO"});
|
||||
it("should dispatch init/load events", async () => {
|
||||
await store.init(new Map(), {type: "FOO"});
|
||||
|
||||
assert.calledOnce(store._messageChannel.simulateMessagesForExistingTabs);
|
||||
});
|
||||
it("should dispatch INIT before LOAD", () => {
|
||||
it("should dispatch INIT before LOAD", async () => {
|
||||
const init = {type: "INIT"};
|
||||
const load = {type: "TAB_LOAD"};
|
||||
sandbox.stub(store, "dispatch");
|
||||
store._messageChannel.simulateMessagesForExistingTabs.callsFake(() => store.dispatch(load));
|
||||
store.init(new Map(), init);
|
||||
await store.init(new Map(), init);
|
||||
|
||||
assert.calledTwice(store.dispatch);
|
||||
assert.equal(store.dispatch.firstCall.args[0], init);
|
||||
|
@ -229,13 +249,13 @@ describe("Store", () => {
|
|||
});
|
||||
});
|
||||
describe("#dispatch", () => {
|
||||
it("should call .onAction of each feed", () => {
|
||||
it("should call .onAction of each feed", async () => {
|
||||
const {dispatch} = store;
|
||||
const sub = {onAction: sinon.spy()};
|
||||
const action = {type: "FOO"};
|
||||
|
||||
store._prefs.set("sub", true);
|
||||
store.init(new Map([["sub", () => sub]]));
|
||||
await store.init(new Map([["sub", () => sub]]));
|
||||
|
||||
dispatch(action);
|
||||
|
||||
|
|
|
@ -85,18 +85,21 @@ describe("Top Sites Feed", () => {
|
|||
"lib/ActivityStreamStorage.jsm": {ActivityStreamStorage: function Fake() {}, getDefaultOptions}
|
||||
}));
|
||||
feed = new TopSitesFeed();
|
||||
feed._storage = {
|
||||
init: sandbox.stub().returns(Promise.resolve()),
|
||||
get: sandbox.stub().returns(Promise.resolve()),
|
||||
set: sandbox.stub().returns(Promise.resolve())
|
||||
const storage = {
|
||||
init: sandbox.stub().resolves(),
|
||||
get: sandbox.stub().resolves(),
|
||||
set: sandbox.stub().resolves()
|
||||
};
|
||||
// Setup for tests that don't call `init` but require feed.storage
|
||||
feed._storage = storage;
|
||||
feed.store = {
|
||||
dispatch: sinon.spy(),
|
||||
getState() { return this.state; },
|
||||
state: {
|
||||
Prefs: {values: {filterAdult: false, topSitesRows: 2}},
|
||||
TopSites: {rows: Array(12).fill("site")}
|
||||
}
|
||||
},
|
||||
dbStorage: {getDbTable: sandbox.stub().returns(storage)}
|
||||
};
|
||||
feed.dedupe.group = (...sites) => sites;
|
||||
links = FAKE_LINKS;
|
||||
|
@ -441,15 +444,33 @@ describe("Top Sites Feed", () => {
|
|||
assert.calledWith(feed._fetchScreenshot, sinon.match.object, "custom");
|
||||
});
|
||||
});
|
||||
describe("#init", () => {
|
||||
it("should call refresh (broadcast:true)", async () => {
|
||||
sandbox.stub(feed, "refresh");
|
||||
|
||||
await feed.init();
|
||||
|
||||
assert.calledOnce(feed.refresh);
|
||||
assert.calledWithExactly(feed.refresh, {broadcast: true});
|
||||
});
|
||||
it("should initialise _tippyTopProvider", async () => {
|
||||
feed._tippyTopProvider.initialized = false;
|
||||
|
||||
await feed.init();
|
||||
|
||||
assert.isTrue(feed._tippyTopProvider.initialized);
|
||||
});
|
||||
it("should initialise the storage", async () => {
|
||||
await feed.init();
|
||||
|
||||
assert.calledOnce(feed.store.dbStorage.getDbTable);
|
||||
assert.calledWithExactly(feed.store.dbStorage.getDbTable, "sectionPrefs");
|
||||
});
|
||||
});
|
||||
describe("#refresh", () => {
|
||||
beforeEach(() => {
|
||||
sandbox.stub(feed, "_fetchIcon");
|
||||
});
|
||||
it("should initialise _tippyTopProvider if it's not already initialised", async () => {
|
||||
feed._tippyTopProvider.initialized = false;
|
||||
await feed.refresh({broadcast: true});
|
||||
assert.ok(feed._tippyTopProvider.initialized);
|
||||
});
|
||||
it("should broadcast TOP_SITES_UPDATED", async () => {
|
||||
sinon.stub(feed, "getLinksWithDefaults").returns(Promise.resolve([]));
|
||||
|
||||
|
@ -492,6 +513,18 @@ describe("Top Sites Feed", () => {
|
|||
|
||||
assert.notCalled(feed._storage.init);
|
||||
});
|
||||
it("should catch indexedDB errors", async () => {
|
||||
feed._storage.get.throws(new Error());
|
||||
globals.sandbox.spy(global.Cu, "reportError");
|
||||
|
||||
try {
|
||||
await feed.refresh({broadcast: false});
|
||||
} catch (e) {
|
||||
assert.fails();
|
||||
}
|
||||
|
||||
assert.calledOnce(Cu.reportError);
|
||||
});
|
||||
});
|
||||
describe("#updateSectionPrefs", () => {
|
||||
it("should call updateSectionPrefs on UPDATE_SECTION_PREFS", () => {
|
||||
|
@ -701,10 +734,10 @@ describe("Top Sites Feed", () => {
|
|||
assert.calledOnce(feed.store.dispatch);
|
||||
assert.propertyVal(feed.store.dispatch.firstCall.args[0], "type", at.TOP_SITES_UPDATED);
|
||||
});
|
||||
it("should call refresh on INIT action", async () => {
|
||||
sinon.stub(feed, "refresh");
|
||||
await feed.onAction({type: at.INIT});
|
||||
assert.calledOnce(feed.refresh);
|
||||
it("should call init on INIT action", async () => {
|
||||
sinon.stub(feed, "init");
|
||||
feed.onAction({type: at.INIT});
|
||||
assert.calledOnce(feed.init);
|
||||
});
|
||||
it("should call refresh on MIGRATION_COMPLETED action", async () => {
|
||||
sinon.stub(feed, "refresh");
|
||||
|
|
Загрузка…
Ссылка в новой задаче