mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			406 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			406 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * 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
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 |