480 lines
17 KiB
JavaScript
480 lines
17 KiB
JavaScript
import {
|
|
_ASRouterPreferences,
|
|
ASRouterPreferences as ASRouterPreferencesSingleton,
|
|
TEST_PROVIDERS,
|
|
} from "modules/ASRouterPreferences.sys.mjs";
|
|
const FAKE_PROVIDERS = [{ id: "foo" }, { id: "bar" }];
|
|
|
|
const PROVIDER_PREF_BRANCH =
|
|
"browser.newtabpage.activity-stream.asrouter.providers.";
|
|
const DEVTOOLS_PREF =
|
|
"browser.newtabpage.activity-stream.asrouter.devtoolsEnabled";
|
|
const CFR_USER_PREF_ADDONS =
|
|
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons";
|
|
const CFR_USER_PREF_FEATURES =
|
|
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features";
|
|
|
|
/** NUMBER_OF_PREFS_TO_OBSERVE includes:
|
|
* 1. asrouter.providers. pref branch
|
|
* 2. asrouter.devtoolsEnabled
|
|
* 3. browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons (user preference - cfr)
|
|
* 4. browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features (user preference - cfr)
|
|
* 5. services.sync.username
|
|
*/
|
|
const NUMBER_OF_PREFS_TO_OBSERVE = 5;
|
|
|
|
describe("ASRouterPreferences", () => {
|
|
let ASRouterPreferences;
|
|
let sandbox;
|
|
let addObserverStub;
|
|
let stringPrefStub;
|
|
let boolPrefStub;
|
|
let resetStub;
|
|
let hasUserValueStub;
|
|
let childListStub;
|
|
let setStringPrefStub;
|
|
|
|
beforeEach(() => {
|
|
ASRouterPreferences = new _ASRouterPreferences();
|
|
|
|
sandbox = sinon.createSandbox();
|
|
addObserverStub = sandbox.stub(global.Services.prefs, "addObserver");
|
|
stringPrefStub = sandbox.stub(global.Services.prefs, "getStringPref");
|
|
resetStub = sandbox.stub(global.Services.prefs, "clearUserPref");
|
|
setStringPrefStub = sandbox.stub(global.Services.prefs, "setStringPref");
|
|
FAKE_PROVIDERS.forEach(provider => {
|
|
stringPrefStub
|
|
.withArgs(`${PROVIDER_PREF_BRANCH}${provider.id}`)
|
|
.returns(JSON.stringify(provider));
|
|
});
|
|
|
|
boolPrefStub = sandbox
|
|
.stub(global.Services.prefs, "getBoolPref")
|
|
.returns(false);
|
|
|
|
hasUserValueStub = sandbox
|
|
.stub(global.Services.prefs, "prefHasUserValue")
|
|
.returns(false);
|
|
|
|
childListStub = sandbox.stub(global.Services.prefs, "getChildList");
|
|
childListStub
|
|
.withArgs(PROVIDER_PREF_BRANCH)
|
|
.returns(
|
|
FAKE_PROVIDERS.map(provider => `${PROVIDER_PREF_BRANCH}${provider.id}`)
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
sandbox.restore();
|
|
});
|
|
|
|
function getPrefNameForProvider(providerId) {
|
|
return `${PROVIDER_PREF_BRANCH}${providerId}`;
|
|
}
|
|
|
|
function setPrefForProvider(providerId, value) {
|
|
stringPrefStub
|
|
.withArgs(getPrefNameForProvider(providerId))
|
|
.returns(JSON.stringify(value));
|
|
}
|
|
|
|
it("ASRouterPreferences should be an instance of _ASRouterPreferences", () => {
|
|
assert.instanceOf(ASRouterPreferencesSingleton, _ASRouterPreferences);
|
|
});
|
|
describe("#init", () => {
|
|
it("should set ._initialized to true", () => {
|
|
ASRouterPreferences.init();
|
|
assert.isTrue(ASRouterPreferences._initialized);
|
|
});
|
|
it("should migrate the provider prefs", () => {
|
|
ASRouterPreferences.uninit();
|
|
// Should be migrated because they contain bucket and not collection
|
|
const MIGRATE_PROVIDERS = [
|
|
{ id: "baz", bucket: "buk" },
|
|
{ id: "qux", bucket: "buk" },
|
|
];
|
|
// Should be cleared to defaults because it throws on setStringPref
|
|
const ERROR_PROVIDER = { id: "err", bucket: "buk" };
|
|
// Should not be migrated because, although modified, it lacks bucket
|
|
const MODIFIED_SAFE_PROVIDER = { id: "safe" };
|
|
const ALL_PROVIDERS = [
|
|
...MIGRATE_PROVIDERS,
|
|
...FAKE_PROVIDERS, // Should not be migrated because they're unmodified
|
|
MODIFIED_SAFE_PROVIDER,
|
|
ERROR_PROVIDER,
|
|
];
|
|
// The migrator should attempt to read prefs for all of these providers
|
|
const TRY_PROVIDERS = [
|
|
...MIGRATE_PROVIDERS,
|
|
MODIFIED_SAFE_PROVIDER,
|
|
ERROR_PROVIDER,
|
|
];
|
|
|
|
// Update the full list of provider prefs
|
|
childListStub
|
|
.withArgs(PROVIDER_PREF_BRANCH)
|
|
.returns(
|
|
ALL_PROVIDERS.map(provider => getPrefNameForProvider(provider.id))
|
|
);
|
|
// Stub the pref values so the migrator can read them
|
|
ALL_PROVIDERS.forEach(provider => {
|
|
stringPrefStub
|
|
.withArgs(getPrefNameForProvider(provider.id))
|
|
.returns(JSON.stringify(provider));
|
|
});
|
|
|
|
// Consider these providers' prefs "modified"
|
|
TRY_PROVIDERS.forEach(provider => {
|
|
hasUserValueStub
|
|
.withArgs(`${PROVIDER_PREF_BRANCH}${provider.id}`)
|
|
.returns(true);
|
|
});
|
|
// Spoof an error when trying to set the pref for this provider so we can
|
|
// test that the pref is gracefully reset on error
|
|
setStringPrefStub
|
|
.withArgs(getPrefNameForProvider(ERROR_PROVIDER.id))
|
|
.throws();
|
|
|
|
ASRouterPreferences.init();
|
|
|
|
// The migrator should have tried to check each pref for user modification
|
|
ALL_PROVIDERS.forEach(provider =>
|
|
assert.calledWith(hasUserValueStub, getPrefNameForProvider(provider.id))
|
|
);
|
|
// Test that we don't call getStringPref for providers that don't have a
|
|
// user-defined value
|
|
FAKE_PROVIDERS.forEach(provider =>
|
|
assert.neverCalledWith(
|
|
stringPrefStub,
|
|
getPrefNameForProvider(provider.id)
|
|
)
|
|
);
|
|
// But we do call it for providers that do have a user-defined value
|
|
TRY_PROVIDERS.forEach(provider =>
|
|
assert.calledWith(stringPrefStub, getPrefNameForProvider(provider.id))
|
|
);
|
|
|
|
// Test that we don't call setStringPref to migrate providers that don't
|
|
// have a bucket property
|
|
assert.neverCalledWith(
|
|
setStringPrefStub,
|
|
getPrefNameForProvider(MODIFIED_SAFE_PROVIDER.id)
|
|
);
|
|
|
|
/**
|
|
* For a given provider, return a sinon matcher that matches if the value
|
|
* looks like a migrated version of the original provider. Requires that:
|
|
* its id matches the original provider's id; it has no bucket; and its
|
|
* collection is set to the value of the original provider's bucket.
|
|
* @param {object} provider the provider object to compare to
|
|
* @returns {object} custom matcher object for sinon
|
|
*/
|
|
function providerJsonMatches(provider) {
|
|
return sandbox.match(migrated => {
|
|
const parsed = JSON.parse(migrated);
|
|
return (
|
|
parsed.id === provider.id &&
|
|
!("bucket" in parsed) &&
|
|
parsed.collection === provider.bucket
|
|
);
|
|
});
|
|
}
|
|
|
|
// Test that we call setStringPref to migrate providers that have a bucket
|
|
// property and don't have a collection property
|
|
MIGRATE_PROVIDERS.forEach(provider =>
|
|
assert.calledWith(
|
|
setStringPrefStub,
|
|
getPrefNameForProvider(provider.id),
|
|
providerJsonMatches(provider) // Verify the migrated pref value
|
|
)
|
|
);
|
|
|
|
// Test that we clear the pref for providers that throw when we try to
|
|
// read or write them
|
|
assert.calledWith(resetStub, getPrefNameForProvider(ERROR_PROVIDER.id));
|
|
});
|
|
it(`should set ${NUMBER_OF_PREFS_TO_OBSERVE} observers and not re-initialize if already initialized`, () => {
|
|
ASRouterPreferences.init();
|
|
assert.callCount(addObserverStub, NUMBER_OF_PREFS_TO_OBSERVE);
|
|
ASRouterPreferences.init();
|
|
ASRouterPreferences.init();
|
|
assert.callCount(addObserverStub, NUMBER_OF_PREFS_TO_OBSERVE);
|
|
});
|
|
});
|
|
describe("#uninit", () => {
|
|
it("should set ._initialized to false", () => {
|
|
ASRouterPreferences.init();
|
|
ASRouterPreferences.uninit();
|
|
assert.isFalse(ASRouterPreferences._initialized);
|
|
});
|
|
it("should clear cached values for ._initialized, .devtoolsEnabled", () => {
|
|
ASRouterPreferences.init();
|
|
// trigger caching
|
|
// eslint-disable-next-line no-unused-vars
|
|
const result = [
|
|
ASRouterPreferences.providers,
|
|
ASRouterPreferences.devtoolsEnabled,
|
|
];
|
|
assert.isNotNull(
|
|
ASRouterPreferences._providers,
|
|
"providers should not be null"
|
|
);
|
|
assert.isNotNull(
|
|
ASRouterPreferences._devtoolsEnabled,
|
|
"devtolosEnabled should not be null"
|
|
);
|
|
|
|
ASRouterPreferences.uninit();
|
|
assert.isNull(ASRouterPreferences._providers);
|
|
assert.isNull(ASRouterPreferences._devtoolsEnabled);
|
|
});
|
|
it("should clear all listeners and remove observers (only once)", () => {
|
|
const removeStub = sandbox.stub(global.Services.prefs, "removeObserver");
|
|
ASRouterPreferences.init();
|
|
ASRouterPreferences.addListener(() => {});
|
|
ASRouterPreferences.addListener(() => {});
|
|
assert.equal(ASRouterPreferences._callbacks.size, 2);
|
|
ASRouterPreferences.uninit();
|
|
// Tests to make sure we don't remove observers that weren't set
|
|
ASRouterPreferences.uninit();
|
|
|
|
assert.callCount(removeStub, NUMBER_OF_PREFS_TO_OBSERVE);
|
|
assert.calledWith(removeStub, PROVIDER_PREF_BRANCH);
|
|
assert.calledWith(removeStub, DEVTOOLS_PREF);
|
|
assert.isEmpty(ASRouterPreferences._callbacks);
|
|
});
|
|
});
|
|
describe(".providers", () => {
|
|
it("should return the value the first time .providers is accessed", () => {
|
|
ASRouterPreferences.init();
|
|
|
|
const result = ASRouterPreferences.providers;
|
|
assert.deepEqual(result, FAKE_PROVIDERS);
|
|
// once per pref
|
|
assert.calledTwice(stringPrefStub);
|
|
});
|
|
it("should return the cached value the second time .providers is accessed", () => {
|
|
ASRouterPreferences.init();
|
|
const [, secondCall] = [
|
|
ASRouterPreferences.providers,
|
|
ASRouterPreferences.providers,
|
|
];
|
|
|
|
assert.deepEqual(secondCall, FAKE_PROVIDERS);
|
|
// once per pref
|
|
assert.calledTwice(stringPrefStub);
|
|
});
|
|
it("should just parse the pref each time if ASRouterPreferences hasn't been initialized yet", () => {
|
|
// Intentionally not initialized
|
|
const [firstCall, secondCall] = [
|
|
ASRouterPreferences.providers,
|
|
ASRouterPreferences.providers,
|
|
];
|
|
|
|
assert.deepEqual(firstCall, FAKE_PROVIDERS);
|
|
assert.deepEqual(secondCall, FAKE_PROVIDERS);
|
|
assert.callCount(stringPrefStub, 4);
|
|
});
|
|
it("should skip the pref without throwing if a pref is not parsable", () => {
|
|
stringPrefStub.withArgs(`${PROVIDER_PREF_BRANCH}foo`).returns("not json");
|
|
ASRouterPreferences.init();
|
|
|
|
assert.deepEqual(ASRouterPreferences.providers, [{ id: "bar" }]);
|
|
});
|
|
it("should include TEST_PROVIDERS if devtools is turned on", () => {
|
|
boolPrefStub.withArgs(DEVTOOLS_PREF).returns(true);
|
|
ASRouterPreferences.init();
|
|
|
|
assert.deepEqual(ASRouterPreferences.providers, [
|
|
...TEST_PROVIDERS,
|
|
...FAKE_PROVIDERS,
|
|
]);
|
|
});
|
|
});
|
|
describe(".devtoolsEnabled", () => {
|
|
it("should read the pref the first time .devtoolsEnabled is accessed", () => {
|
|
ASRouterPreferences.init();
|
|
|
|
const result = ASRouterPreferences.devtoolsEnabled;
|
|
assert.deepEqual(result, false);
|
|
assert.calledOnce(boolPrefStub);
|
|
});
|
|
it("should return the cached value the second time .devtoolsEnabled is accessed", () => {
|
|
ASRouterPreferences.init();
|
|
const [, secondCall] = [
|
|
ASRouterPreferences.devtoolsEnabled,
|
|
ASRouterPreferences.devtoolsEnabled,
|
|
];
|
|
|
|
assert.deepEqual(secondCall, false);
|
|
assert.calledOnce(boolPrefStub);
|
|
});
|
|
it("should just parse the pref each time if ASRouterPreferences hasn't been initialized yet", () => {
|
|
// Intentionally not initialized
|
|
const [firstCall, secondCall] = [
|
|
ASRouterPreferences.devtoolsEnabled,
|
|
ASRouterPreferences.devtoolsEnabled,
|
|
];
|
|
|
|
assert.deepEqual(firstCall, false);
|
|
assert.deepEqual(secondCall, false);
|
|
assert.calledTwice(boolPrefStub);
|
|
});
|
|
});
|
|
describe("#getAllUserPreferences", () => {
|
|
it("should return all user preferences", () => {
|
|
boolPrefStub.withArgs(CFR_USER_PREF_ADDONS).returns(false);
|
|
boolPrefStub.withArgs(CFR_USER_PREF_FEATURES).returns(true);
|
|
const result = ASRouterPreferences.getAllUserPreferences();
|
|
assert.deepEqual(result, {
|
|
cfrAddons: false,
|
|
cfrFeatures: true,
|
|
});
|
|
});
|
|
});
|
|
describe("#enableOrDisableProvider", () => {
|
|
it("should enable an existing provider if second param is true", () => {
|
|
setPrefForProvider("foo", { id: "foo", enabled: false });
|
|
assert.isFalse(ASRouterPreferences.providers[0].enabled);
|
|
|
|
ASRouterPreferences.enableOrDisableProvider("foo", true);
|
|
|
|
assert.calledWith(
|
|
setStringPrefStub,
|
|
getPrefNameForProvider("foo"),
|
|
JSON.stringify({ id: "foo", enabled: true })
|
|
);
|
|
});
|
|
it("should disable an existing provider if second param is false", () => {
|
|
setPrefForProvider("foo", { id: "foo", enabled: true });
|
|
assert.isTrue(ASRouterPreferences.providers[0].enabled);
|
|
|
|
ASRouterPreferences.enableOrDisableProvider("foo", false);
|
|
|
|
assert.calledWith(
|
|
setStringPrefStub,
|
|
getPrefNameForProvider("foo"),
|
|
JSON.stringify({ id: "foo", enabled: false })
|
|
);
|
|
});
|
|
it("should not throw if the id does not exist", () => {
|
|
assert.doesNotThrow(() => {
|
|
ASRouterPreferences.enableOrDisableProvider("does_not_exist", true);
|
|
});
|
|
});
|
|
it("should not throw if pref is not parseable", () => {
|
|
stringPrefStub
|
|
.withArgs(getPrefNameForProvider("foo"))
|
|
.returns("not valid");
|
|
assert.doesNotThrow(() => {
|
|
ASRouterPreferences.enableOrDisableProvider("foo", true);
|
|
});
|
|
});
|
|
});
|
|
describe("#setUserPreference", () => {
|
|
it("should do nothing if the pref doesn't exist", () => {
|
|
ASRouterPreferences.setUserPreference("foo", true);
|
|
assert.notCalled(boolPrefStub);
|
|
});
|
|
it("should set the given pref", () => {
|
|
const setStub = sandbox.stub(global.Services.prefs, "setBoolPref");
|
|
ASRouterPreferences.setUserPreference("cfrAddons", true);
|
|
assert.calledWith(setStub, CFR_USER_PREF_ADDONS, true);
|
|
});
|
|
});
|
|
describe("#resetProviderPref", () => {
|
|
it("should reset the pref and user prefs", () => {
|
|
ASRouterPreferences.resetProviderPref();
|
|
FAKE_PROVIDERS.forEach(provider => {
|
|
assert.calledWith(resetStub, getPrefNameForProvider(provider.id));
|
|
});
|
|
assert.calledWith(resetStub, CFR_USER_PREF_ADDONS);
|
|
assert.calledWith(resetStub, CFR_USER_PREF_FEATURES);
|
|
});
|
|
});
|
|
describe("observer, listeners", () => {
|
|
it("should invalidate .providers when the pref is changed", () => {
|
|
const testProvider = { id: "newstuff" };
|
|
const newProviders = [...FAKE_PROVIDERS, testProvider];
|
|
|
|
ASRouterPreferences.init();
|
|
|
|
assert.deepEqual(ASRouterPreferences.providers, FAKE_PROVIDERS);
|
|
stringPrefStub
|
|
.withArgs(getPrefNameForProvider(testProvider.id))
|
|
.returns(JSON.stringify(testProvider));
|
|
childListStub
|
|
.withArgs(PROVIDER_PREF_BRANCH)
|
|
.returns(
|
|
newProviders.map(provider => getPrefNameForProvider(provider.id))
|
|
);
|
|
ASRouterPreferences.observe(
|
|
null,
|
|
null,
|
|
getPrefNameForProvider(testProvider.id)
|
|
);
|
|
|
|
// Cache should be invalidated so we access the new value of the pref now
|
|
assert.deepEqual(ASRouterPreferences.providers, newProviders);
|
|
});
|
|
it("should invalidate .devtoolsEnabled and .providers when the pref is changed", () => {
|
|
ASRouterPreferences.init();
|
|
|
|
assert.isFalse(ASRouterPreferences.devtoolsEnabled);
|
|
boolPrefStub.withArgs(DEVTOOLS_PREF).returns(true);
|
|
childListStub.withArgs(PROVIDER_PREF_BRANCH).returns([]);
|
|
ASRouterPreferences.observe(null, null, DEVTOOLS_PREF);
|
|
|
|
// Cache should be invalidated so we access the new value of the pref now
|
|
// Note that providers needs to be invalidated because devtools adds test content to it.
|
|
assert.isTrue(ASRouterPreferences.devtoolsEnabled);
|
|
assert.deepEqual(ASRouterPreferences.providers, TEST_PROVIDERS);
|
|
});
|
|
it("should call listeners added with .addListener", () => {
|
|
const callback1 = sinon.stub();
|
|
const callback2 = sinon.stub();
|
|
ASRouterPreferences.init();
|
|
ASRouterPreferences.addListener(callback1);
|
|
ASRouterPreferences.addListener(callback2);
|
|
|
|
ASRouterPreferences.observe(null, null, getPrefNameForProvider("foo"));
|
|
assert.calledWith(callback1, getPrefNameForProvider("foo"));
|
|
|
|
ASRouterPreferences.observe(null, null, DEVTOOLS_PREF);
|
|
assert.calledWith(callback2, DEVTOOLS_PREF);
|
|
});
|
|
it("should not call listeners after they are removed with .removeListeners", () => {
|
|
const callback = sinon.stub();
|
|
ASRouterPreferences.init();
|
|
ASRouterPreferences.addListener(callback);
|
|
|
|
ASRouterPreferences.observe(null, null, getPrefNameForProvider("foo"));
|
|
assert.calledWith(callback, getPrefNameForProvider("foo"));
|
|
|
|
callback.reset();
|
|
ASRouterPreferences.removeListener(callback);
|
|
|
|
ASRouterPreferences.observe(null, null, DEVTOOLS_PREF);
|
|
assert.notCalled(callback);
|
|
});
|
|
});
|
|
describe("#_transformPersonalizedCfrScores", () => {
|
|
it("should report JSON.parse errors", () => {
|
|
sandbox.stub(global.console, "error");
|
|
|
|
ASRouterPreferences._transformPersonalizedCfrScores("");
|
|
|
|
assert.calledOnce(global.console.error);
|
|
});
|
|
it("should return an object parsed from a string", () => {
|
|
const scores = { FOO: 3000, BAR: 4000 };
|
|
assert.deepEqual(
|
|
ASRouterPreferences._transformPersonalizedCfrScores(
|
|
JSON.stringify(scores)
|
|
),
|
|
scores
|
|
);
|
|
});
|
|
});
|
|
});
|