diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/newtab/test/unit/utils.js | 406 |
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 + }); + } +} |