summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/unit/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/test/unit/utils.js')
-rw-r--r--browser/components/newtab/test/unit/utils.js406
1 files changed, 406 insertions, 0 deletions
diff --git a/browser/components/newtab/test/unit/utils.js b/browser/components/newtab/test/unit/utils.js
new file mode 100644
index 0000000000..22069b8635
--- /dev/null
+++ b/browser/components/newtab/test/unit/utils.js
@@ -0,0 +1,406 @@
+/**
+ * GlobalOverrider - Utility that allows you to override properties on the global object.
+ * See unit-entry.js for example usage.
+ */
+export class GlobalOverrider {
+ constructor() {
+ this.originalGlobals = new Map();
+ this.sandbox = sinon.createSandbox();
+ }
+
+ /**
+ * _override - Internal method to override properties on the global object.
+ * The first time a given key is overridden, we cache the original
+ * value in this.originalGlobals so that later it can be restored.
+ *
+ * @param {string} key The identifier of the property
+ * @param {any} value The value to which the property should be reassigned
+ */
+ _override(key, value) {
+ if (!this.originalGlobals.has(key)) {
+ this.originalGlobals.set(key, global[key]);
+ }
+ global[key] = value;
+ }
+
+ /**
+ * set - Override a given property, or all properties on an object
+ *
+ * @param {string|object} key If a string, the identifier of the property
+ * If an object, a number of properties and values to which they should be reassigned.
+ * @param {any} value The value to which the property should be reassigned
+ * @return {type} description
+ */
+ set(key, value) {
+ if (!value && typeof key === "object") {
+ const overrides = key;
+ Object.keys(overrides).forEach(k => this._override(k, overrides[k]));
+ } else {
+ this._override(key, value);
+ }
+ return value;
+ }
+
+ /**
+ * reset - Reset the global sandbox, so all state on spies, stubs etc. is cleared.
+ * You probably want to call this after each test.
+ */
+ reset() {
+ this.sandbox.reset();
+ }
+
+ /**
+ * restore - Restore the global sandbox and reset all overriden properties to
+ * their original values. You should call this after all tests have completed.
+ */
+ restore() {
+ this.sandbox.restore();
+ this.originalGlobals.forEach((value, key) => {
+ global[key] = value;
+ });
+ }
+}
+
+/**
+ * A map of mocked preference names and values, used by `FakensIPrefBranch`,
+ * `FakensIPrefService`, and `FakePrefs`.
+ *
+ * Tests should add entries to this map for any preferences they'd like to set,
+ * and remove any entries during teardown for preferences that shouldn't be
+ * shared between tests.
+ */
+export const FAKE_GLOBAL_PREFS = new Map();
+
+/**
+ * Very simple fake for the most basic semantics of nsIPrefBranch. Lots of
+ * things aren't yet supported. Feel free to add them in.
+ *
+ * @param {Object} args - optional arguments
+ * @param {Function} args.initHook - if present, will be called back
+ * inside the constructor. Typically used from tests
+ * to save off a pointer to the created instance so that
+ * stubs and spies can be inspected by the test code.
+ */
+export class FakensIPrefBranch {
+ PREF_INVALID = "invalid";
+ PREF_INT = "integer";
+ PREF_BOOL = "boolean";
+ PREF_STRING = "string";
+
+ constructor(args) {
+ if (args) {
+ if ("initHook" in args) {
+ args.initHook.call(this);
+ }
+ if (args.defaultBranch) {
+ this.prefs = new Map();
+ } else {
+ this.prefs = FAKE_GLOBAL_PREFS;
+ }
+ } else {
+ this.prefs = FAKE_GLOBAL_PREFS;
+ }
+ this._prefBranch = {};
+ this.observers = new Map();
+ }
+ addObserver(prefix, callback) {
+ this.observers.set(prefix, callback);
+ }
+ removeObserver(prefix, callback) {
+ this.observers.delete(prefix, callback);
+ }
+ setStringPref(prefName, value) {
+ this.set(prefName, value);
+ }
+ getStringPref(prefName, defaultValue) {
+ return this.get(prefName, defaultValue);
+ }
+ setBoolPref(prefName, value) {
+ this.set(prefName, value);
+ }
+ getBoolPref(prefName) {
+ return this.get(prefName);
+ }
+ setIntPref(prefName, value) {
+ this.set(prefName, value);
+ }
+ getIntPref(prefName) {
+ return this.get(prefName);
+ }
+ setCharPref(prefName, value) {
+ this.set(prefName, value);
+ }
+ getCharPref(prefName) {
+ return this.get(prefName);
+ }
+ clearUserPref(prefName) {
+ this.prefs.delete(prefName);
+ }
+ get(prefName, defaultValue) {
+ let value = this.prefs.get(prefName);
+ return typeof value === "undefined" ? defaultValue : value;
+ }
+ getPrefType(prefName) {
+ let value = this.prefs.get(prefName);
+ switch (typeof value) {
+ case "number":
+ return this.PREF_INT;
+
+ case "boolean":
+ return this.PREF_BOOL;
+
+ case "string":
+ return this.PREF_STRING;
+
+ default:
+ return this.PREF_INVALID;
+ }
+ }
+ set(prefName, value) {
+ this.prefs.set(prefName, value);
+
+ // Trigger all observers for prefixes of the changed pref name. This matches
+ // the semantics of `nsIPrefBranch`.
+ let observerPrefixes = [...this.observers.keys()].filter(prefix =>
+ prefName.startsWith(prefix)
+ );
+ for (let observerPrefix of observerPrefixes) {
+ this.observers.get(observerPrefix)("", "", prefName);
+ }
+ }
+ getChildList(prefix) {
+ return [...this.prefs.keys()].filter(prefName =>
+ prefName.startsWith(prefix)
+ );
+ }
+ prefHasUserValue(prefName) {
+ return this.prefs.has(prefName);
+ }
+ prefIsLocked(prefName) {
+ return false;
+ }
+}
+
+/**
+ * A fake `Services.prefs` implementation that extends `FakensIPrefBranch`
+ * with methods specific to `nsIPrefService`.
+ */
+export class FakensIPrefService extends FakensIPrefBranch {
+ getBranch() {}
+ getDefaultBranch(prefix) {
+ return {
+ setBoolPref() {},
+ setIntPref() {},
+ setStringPref() {},
+ clearUserPref() {},
+ };
+ }
+}
+
+/**
+ * Very simple fake for the most basic semantics of Preferences.sys.mjs.
+ * Extends FakensIPrefBranch.
+ */
+export class FakePrefs extends FakensIPrefBranch {
+ observe(prefName, callback) {
+ super.addObserver(prefName, callback);
+ }
+ ignore(prefName, callback) {
+ super.removeObserver(prefName, callback);
+ }
+ observeBranch(listener) {}
+ ignoreBranch(listener) {}
+ set(prefName, value) {
+ this.prefs.set(prefName, value);
+
+ // Trigger observers for just the changed pref name, not any of its
+ // prefixes. This matches the semantics of `Preferences.sys.mjs`.
+ if (this.observers.has(prefName)) {
+ this.observers.get(prefName)(value);
+ }
+ }
+}
+
+/**
+ * Slimmed down version of toolkit/modules/EventEmitter.sys.mjs
+ */
+export function EventEmitter() {}
+EventEmitter.decorate = function (objectToDecorate) {
+ let emitter = new EventEmitter();
+ objectToDecorate.on = emitter.on.bind(emitter);
+ objectToDecorate.off = emitter.off.bind(emitter);
+ objectToDecorate.once = emitter.once.bind(emitter);
+ objectToDecorate.emit = emitter.emit.bind(emitter);
+};
+EventEmitter.prototype = {
+ on(event, listener) {
+ if (!this._eventEmitterListeners) {
+ this._eventEmitterListeners = new Map();
+ }
+ if (!this._eventEmitterListeners.has(event)) {
+ this._eventEmitterListeners.set(event, []);
+ }
+ this._eventEmitterListeners.get(event).push(listener);
+ },
+ off(event, listener) {
+ if (!this._eventEmitterListeners) {
+ return;
+ }
+ let listeners = this._eventEmitterListeners.get(event);
+ if (listeners) {
+ this._eventEmitterListeners.set(
+ event,
+ listeners.filter(
+ l => l !== listener && l._originalListener !== listener
+ )
+ );
+ }
+ },
+ once(event, listener) {
+ return new Promise(resolve => {
+ let handler = (_, first, ...rest) => {
+ this.off(event, handler);
+ if (listener) {
+ listener(event, first, ...rest);
+ }
+ resolve(first);
+ };
+
+ handler._originalListener = listener;
+ this.on(event, handler);
+ });
+ },
+ // All arguments to this method will be sent to listeners
+ emit(event, ...args) {
+ if (
+ !this._eventEmitterListeners ||
+ !this._eventEmitterListeners.has(event)
+ ) {
+ return;
+ }
+ let originalListeners = this._eventEmitterListeners.get(event);
+ for (let listener of this._eventEmitterListeners.get(event)) {
+ // If the object was destroyed during event emission, stop
+ // emitting.
+ if (!this._eventEmitterListeners) {
+ break;
+ }
+ // If listeners were removed during emission, make sure the
+ // event handler we're going to fire wasn't removed.
+ if (
+ originalListeners === this._eventEmitterListeners.get(event) ||
+ this._eventEmitterListeners.get(event).some(l => l === listener)
+ ) {
+ try {
+ listener(event, ...args);
+ } catch (ex) {
+ // error with a listener
+ }
+ }
+ }
+ },
+};
+
+export function FakePerformance() {}
+FakePerformance.prototype = {
+ marks: new Map(),
+ now() {
+ return window.performance.now();
+ },
+ timing: { navigationStart: 222222.123 },
+ get timeOrigin() {
+ return 10000.234;
+ },
+ // XXX assumes type == "mark"
+ getEntriesByName(name, type) {
+ if (this.marks.has(name)) {
+ return this.marks.get(name);
+ }
+ return [];
+ },
+ callsToMark: 0,
+
+ /**
+ * @note The "startTime" for each mark is simply the number of times mark
+ * has been called in this object.
+ */
+ mark(name) {
+ let markObj = {
+ name,
+ entryType: "mark",
+ startTime: ++this.callsToMark,
+ duration: 0,
+ };
+
+ if (this.marks.has(name)) {
+ this.marks.get(name).push(markObj);
+ return;
+ }
+
+ this.marks.set(name, [markObj]);
+ },
+};
+
+/**
+ * addNumberReducer - a simple dummy reducer for testing that adds a number
+ */
+export function addNumberReducer(prevState = 0, action) {
+ return action.type === "ADD" ? prevState + action.data : prevState;
+}
+
+export class FakeConsoleAPI {
+ static LOG_LEVELS = {
+ all: Number.MIN_VALUE,
+ debug: 2,
+ log: 3,
+ info: 3,
+ clear: 3,
+ trace: 3,
+ timeEnd: 3,
+ time: 3,
+ assert: 3,
+ group: 3,
+ groupEnd: 3,
+ profile: 3,
+ profileEnd: 3,
+ dir: 3,
+ dirxml: 3,
+ warn: 4,
+ error: 5,
+ off: Number.MAX_VALUE,
+ };
+
+ constructor({ prefix = "", maxLogLevel = "all" } = {}) {
+ this.prefix = prefix;
+ this.prefixStr = prefix ? `${prefix}: ` : "";
+ this.maxLogLevel = maxLogLevel;
+
+ for (const level of Object.keys(FakeConsoleAPI.LOG_LEVELS)) {
+ // eslint-disable-next-line no-console
+ if (typeof console[level] === "function") {
+ this[level] = this.shouldLog(level)
+ ? this._log.bind(this, level)
+ : () => {};
+ }
+ }
+ }
+ shouldLog(level) {
+ return (
+ FakeConsoleAPI.LOG_LEVELS[this.maxLogLevel] <=
+ FakeConsoleAPI.LOG_LEVELS[level]
+ );
+ }
+ _log(level, ...args) {
+ console[level](this.prefixStr, ...args); // eslint-disable-line no-console
+ }
+}
+
+export class FakeLogger extends FakeConsoleAPI {
+ constructor() {
+ super({
+ // Don't use a prefix because the first instance gets cached and reused by
+ // other consumers that would otherwise pass their own identifying prefix.
+ maxLogLevel: "off", // Change this to "debug" or "all" to get more logging in tests
+ });
+ }
+}