diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /devtools/client/shared/test-helpers | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/shared/test-helpers')
14 files changed, 892 insertions, 0 deletions
diff --git a/devtools/client/shared/test-helpers/jest-fixtures/ChromeUtils.js b/devtools/client/shared/test-helpers/jest-fixtures/ChromeUtils.js new file mode 100644 index 0000000000..2fee8bc01c --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/ChromeUtils.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = { + import: () => ({}), + addProfilerMarker: () => {}, + defineESModuleGetters: () => {}, + importESModule: () => ({}), +}; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/Services.js b/devtools/client/shared/test-helpers/jest-fixtures/Services.js new file mode 100644 index 0000000000..85d0bce5e0 --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/Services.js @@ -0,0 +1,566 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* globals localStorage, window */ + +// XXX: This file is a copy of the Services shim from devtools-services. +// See https://github.com/firefox-devtools/devtools-core/blob/a9263b4c3f88ea42879a36cdc3ca8217b4a528ea/packages/devtools-services/index.js +// Many Jest tests in the debugger rely on preferences, but can't use Services. +// This fixture is probably doing too much and should be reduced to the minimum +// needed to pass the tests. + +/* eslint-disable mozilla/valid-services */ + +// Some constants from nsIPrefBranch.idl. +const PREF_INVALID = 0; +const PREF_STRING = 32; +const PREF_INT = 64; +const PREF_BOOL = 128; +const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; + +// We prefix all our local storage items with this. +const PREFIX = "Services.prefs:"; + +/** + * Create a new preference branch. This object conforms largely to + * nsIPrefBranch and nsIPrefService, though it only implements the + * subset needed by devtools. A preference branch can hold child + * preferences while also holding a preference value itself. + * + * @param {PrefBranch} parent the parent branch, or null for the root + * branch. + * @param {String} name the base name of this branch + * @param {String} fullName the fully-qualified name of this branch + */ +function PrefBranch(parent, name, fullName) { + this._parent = parent; + this._name = name; + this._fullName = fullName; + this._observers = {}; + this._children = {}; + + // Properties used when this branch has a value as well. + this._defaultValue = null; + this._hasUserValue = false; + this._userValue = null; + this._type = PREF_INVALID; +} + +PrefBranch.prototype = { + PREF_INVALID, + PREF_STRING, + PREF_INT, + PREF_BOOL, + + /** @see nsIPrefBranch.root. */ + get root() { + return this._fullName; + }, + + /** @see nsIPrefBranch.getPrefType. */ + getPrefType(prefName) { + return this._findPref(prefName)._type; + }, + + /** @see nsIPrefBranch.getBoolPref. */ + getBoolPref(prefName, defaultValue) { + try { + const thePref = this._findPref(prefName); + if (thePref._type !== PREF_BOOL) { + throw new Error(`${prefName} does not have bool type`); + } + return thePref._get(); + } catch (e) { + if (typeof defaultValue !== "undefined") { + return defaultValue; + } + throw e; + } + }, + + /** @see nsIPrefBranch.setBoolPref. */ + setBoolPref(prefName, value) { + if (typeof value !== "boolean") { + throw new Error("non-bool passed to setBoolPref"); + } + const thePref = this._findOrCreatePref(prefName, value, true, value); + if (thePref._type !== PREF_BOOL) { + throw new Error(`${prefName} does not have bool type`); + } + thePref._set(value); + }, + + /** @see nsIPrefBranch.getCharPref. */ + getCharPref(prefName, defaultValue) { + try { + const thePref = this._findPref(prefName); + if (thePref._type !== PREF_STRING) { + throw new Error(`${prefName} does not have string type`); + } + return thePref._get(); + } catch (e) { + if (typeof defaultValue !== "undefined") { + return defaultValue; + } + throw e; + } + }, + + /** @see nsIPrefBranch.getStringPref. */ + getStringPref() { + return this.getCharPref.apply(this, arguments); + }, + + /** @see nsIPrefBranch.setCharPref. */ + setCharPref(prefName, value) { + if (typeof value !== "string") { + throw new Error("non-string passed to setCharPref"); + } + const thePref = this._findOrCreatePref(prefName, value, true, value); + if (thePref._type !== PREF_STRING) { + throw new Error(`${prefName} does not have string type`); + } + thePref._set(value); + }, + + /** @see nsIPrefBranch.setStringPref. */ + setStringPref() { + return this.setCharPref.apply(this, arguments); + }, + + /** @see nsIPrefBranch.getIntPref. */ + getIntPref(prefName, defaultValue) { + try { + const thePref = this._findPref(prefName); + if (thePref._type !== PREF_INT) { + throw new Error(`${prefName} does not have int type`); + } + return thePref._get(); + } catch (e) { + if (typeof defaultValue !== "undefined") { + return defaultValue; + } + throw e; + } + }, + + /** @see nsIPrefBranch.setIntPref. */ + setIntPref(prefName, value) { + if (typeof value !== "number") { + throw new Error("non-number passed to setIntPref"); + } + const thePref = this._findOrCreatePref(prefName, value, true, value); + if (thePref._type !== PREF_INT) { + throw new Error(`${prefName} does not have int type`); + } + thePref._set(value); + }, + + /** @see nsIPrefBranch.clearUserPref */ + clearUserPref(prefName) { + const thePref = this._findPref(prefName); + thePref._clearUserValue(); + }, + + /** @see nsIPrefBranch.prefHasUserValue */ + prefHasUserValue(prefName) { + const thePref = this._findPref(prefName); + return thePref._hasUserValue; + }, + + /** @see nsIPrefBranch.addObserver */ + addObserver(domain, observer, holdWeak) { + if (holdWeak) { + throw new Error("shim prefs only supports strong observers"); + } + + if (!(domain in this._observers)) { + this._observers[domain] = []; + } + this._observers[domain].push(observer); + }, + + /** @see nsIPrefBranch.removeObserver */ + removeObserver(domain, observer) { + if (!(domain in this._observers)) { + return; + } + const index = this._observers[domain].indexOf(observer); + if (index >= 0) { + this._observers[domain].splice(index, 1); + } + }, + + /** @see nsIPrefService.savePrefFile */ + savePrefFile(file) { + if (file) { + throw new Error("shim prefs only supports null file in savePrefFile"); + } + // Nothing to do - this implementation always writes back. + }, + + /** @see nsIPrefService.getBranch */ + getBranch(prefRoot) { + if (!prefRoot) { + return this; + } + if (prefRoot.endsWith(".")) { + prefRoot = prefRoot.slice(0, -1); + } + // This is a bit weird since it could erroneously return a pref, + // not a pref branch. + return this._findPref(prefRoot); + }, + + /** + * Return this preference's current value. + * + * @return {Any} The current value of this preference. This may + * return a string, a number, or a boolean depending on the + * preference's type. + */ + _get() { + if (this._hasUserValue) { + return this._userValue; + } + return this._defaultValue; + }, + + /** + * Set the preference's value. The new value is assumed to be a + * user value. After setting the value, this function emits a + * change notification. + * + * @param {Any} value the new value + */ + _set(value) { + if (!this._hasUserValue || value !== this._userValue) { + this._userValue = value; + this._hasUserValue = true; + this._saveAndNotify(); + } + }, + + /** + * Set the default value for this preference, and emit a + * notification if this results in a visible change. + * + * @param {Any} value the new default value + */ + _setDefault(value) { + if (this._defaultValue !== value) { + this._defaultValue = value; + if (!this._hasUserValue) { + this._saveAndNotify(); + } + } + }, + + /** + * If this preference has a user value, clear it. If a change was + * made, emit a change notification. + */ + _clearUserValue() { + if (this._hasUserValue) { + this._userValue = null; + this._hasUserValue = false; + this._saveAndNotify(); + } + }, + + /** + * Helper function to write the preference's value to local storage + * and then emit a change notification. + */ + _saveAndNotify() { + const store = { + type: this._type, + defaultValue: this._defaultValue, + hasUserValue: this._hasUserValue, + userValue: this._userValue, + }; + + localStorage.setItem(PREFIX + this._fullName, JSON.stringify(store)); + this._parent._notify(this._name); + }, + + /** + * Change this preference's value without writing it back to local + * storage. This is used to handle changes to local storage that + * were made externally. + * + * @param {Number} type one of the PREF_* values + * @param {Any} userValue the user value to use if the pref does not exist + * @param {Any} defaultValue the default value to use if the pref + * does not exist + * @param {Boolean} hasUserValue if a new pref is created, whether + * the default value is also a user value + * @param {Object} store the new value of the preference. It should + * be of the form {type, defaultValue, hasUserValue, userValue}; + * where |type| is one of the PREF_* type constants; |defaultValue| + * and |userValue| are the default and user values, respectively; + * and |hasUserValue| is a boolean indicating whether the user value + * is valid + */ + _storageUpdated(type, userValue, hasUserValue, defaultValue) { + this._type = type; + this._defaultValue = defaultValue; + this._hasUserValue = hasUserValue; + this._userValue = userValue; + // There's no need to write this back to local storage, since it + // came from there; and this avoids infinite event loops. + this._parent._notify(this._name); + }, + + /** + * Helper function to find either a Preference or PrefBranch object + * given its name. If the name is not found, throws an exception. + * + * @param {String} prefName the fully-qualified preference name + * @return {Object} Either a Preference or PrefBranch object + */ + _findPref(prefName) { + const branchNames = prefName.split("."); + let branch = this; + + for (const branchName of branchNames) { + branch = branch._children[branchName]; + if (!branch) { + // throw new Error(`could not find pref branch ${ prefName}`); + return false; + } + } + + return branch; + }, + + /** + * Helper function to notify any observers when a preference has + * changed. This will also notify the parent branch for further + * reporting. + * + * @param {String} relativeName the name of the updated pref, + * relative to this branch + */ + _notify(relativeName) { + for (const domain in this._observers) { + if ( + relativeName === domain || + domain === "" || + (domain.endsWith(".") && relativeName.startsWith(domain)) + ) { + // Allow mutation while walking. + const localList = this._observers[domain].slice(); + for (const observer of localList) { + try { + if ("observe" in observer) { + observer.observe( + this, + NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, + relativeName + ); + } else { + // Function-style observer -- these aren't mentioned in + // the IDL, but they're accepted and devtools uses them. + observer(this, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, relativeName); + } + } catch (e) { + console.error(e); + } + } + } + } + + if (this._parent) { + this._parent._notify(`${this._name}.${relativeName}`); + } + }, + + /** + * Helper function to create a branch given an array of branch names + * representing the path of the new branch. + * + * @param {Array} branchList an array of strings, one per component + * of the branch to be created + * @return {PrefBranch} the new branch + */ + _createBranch(branchList) { + let parent = this; + for (const branch of branchList) { + if (!parent._children[branch]) { + const isParentRoot = !parent._parent; + const branchName = (isParentRoot ? "" : `${parent.root}.`) + branch; + parent._children[branch] = new PrefBranch(parent, branch, branchName); + } + parent = parent._children[branch]; + } + return parent; + }, + + /** + * Create a new preference. The new preference is assumed to be in + * local storage already, and the new value is taken from there. + * + * @param {String} keyName the full-qualified name of the preference. + * This is also the name of the key in local storage. + * @param {Any} userValue the user value to use if the pref does not exist + * @param {Boolean} hasUserValue if a new pref is created, whether + * the default value is also a user value + * @param {Any} defaultValue the default value to use if the pref + * does not exist + * @param {Boolean} init if true, then this call is initialization + * from local storage and should override the default prefs + */ + _findOrCreatePref( + keyName, + userValue, + hasUserValue, + defaultValue, + init = false + ) { + const branch = this._createBranch(keyName.split(".")); + + if (hasUserValue && typeof userValue !== typeof defaultValue) { + throw new Error(`inconsistent values when creating ${keyName}`); + } + + let type; + switch (typeof defaultValue) { + case "boolean": + type = PREF_BOOL; + break; + case "number": + type = PREF_INT; + break; + case "string": + type = PREF_STRING; + break; + default: + throw new Error(`unhandled argument type: ${typeof defaultValue}`); + } + + if (init || branch._type === PREF_INVALID) { + branch._storageUpdated(type, userValue, hasUserValue, defaultValue); + } else if (branch._type !== type) { + throw new Error(`attempt to change type of pref ${keyName}`); + } + + return branch; + }, + + getKeyName(keyName) { + if (keyName.startsWith(PREFIX)) { + return keyName.slice(PREFIX.length); + } + + return keyName; + }, + + /** + * Helper function that is called when local storage changes. This + * updates the preferences and notifies pref observers as needed. + * + * @param {StorageEvent} event the event representing the local + * storage change + */ + _onStorageChange(event) { + if (event.storageArea !== localStorage) { + return; + } + + const key = this.getKeyName(event.key); + + // Ignore delete events. Not clear what's correct. + if (key === null || event.newValue === null) { + return; + } + + const { type, userValue, hasUserValue, defaultValue } = JSON.parse( + event.newValue + ); + if (event.oldValue === null) { + this._findOrCreatePref(key, userValue, hasUserValue, defaultValue); + } else { + const thePref = this._findPref(key); + thePref._storageUpdated(type, userValue, hasUserValue, defaultValue); + } + }, + + /** + * Helper function to initialize the root PrefBranch. + */ + _initializeRoot() { + if (Services._defaultPrefsEnabled) { + /* eslint-disable no-eval */ + // let devtools = require("raw!prefs!devtools/client/preferences/devtools"); + // eval(devtools); + // let all = require("raw!prefs!modules/libpref/init/all"); + // eval(all); + /* eslint-enable no-eval */ + } + + // Read the prefs from local storage and create the local + // representations. + for (let i = 0; i < localStorage.length; ++i) { + const keyName = localStorage.key(i); + if (keyName.startsWith(PREFIX)) { + const { userValue, hasUserValue, defaultValue } = JSON.parse( + localStorage.getItem(keyName) + ); + this._findOrCreatePref( + keyName.slice(PREFIX.length), + userValue, + hasUserValue, + defaultValue, + true + ); + } + } + + this._onStorageChange = this._onStorageChange.bind(this); + window.addEventListener("storage", this._onStorageChange); + }, +}; + +const Services = { + _prefs: null, + + _defaultPrefsEnabled: true, + + get prefs() { + if (!this._prefs) { + this._prefs = new PrefBranch(null, "", ""); + this._prefs._initializeRoot(); + } + return this._prefs; + }, + + appinfo: "", + obs: { addObserver: () => {} }, + strings: { + createBundle(bundle) { + return { + GetStringFromName(str) { + return "NodeTest"; + }, + }; + }, + }, + intl: { + stringHasRTLChars: () => false, + }, +}; + +function pref(name, value) { + // eslint-disable-next-line mozilla/valid-services-property + const thePref = Services.prefs._findOrCreatePref(name, value, true, value); + thePref._setDefault(value); +} + +module.exports = Services; +Services.pref = pref; +Services.uuid = { generateUUID: () => {} }; +Services.dns = {}; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/devtools-utils.js b/devtools/client/shared/test-helpers/jest-fixtures/devtools-utils.js new file mode 100644 index 0000000000..baa0647728 --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/devtools-utils.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +module.exports = { + getTopWindow(win) { + return win.top; + }, + defineLazyGetter() {}, + makeInfallible: fn => fn, +}; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/empty-module.js b/devtools/client/shared/test-helpers/jest-fixtures/empty-module.js new file mode 100644 index 0000000000..67bbdf35ca --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/empty-module.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = {}; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/fluent-l10n.js b/devtools/client/shared/test-helpers/jest-fixtures/fluent-l10n.js new file mode 100644 index 0000000000..186ca00342 --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/fluent-l10n.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/** + * Mock for devtools/client/shared/modules/fluent-l10n/fluent-l10n + */ +class FluentL10n { + async init() {} + + getBundles() { + return []; + } + + getString(id, args) { + return args ? `${id}__${JSON.stringify(args)}` : id; + } +} + +// Export the class +exports.FluentL10n = FluentL10n; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/generate-uuid.js b/devtools/client/shared/test-helpers/jest-fixtures/generate-uuid.js new file mode 100644 index 0000000000..3f53c7e0de --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/generate-uuid.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function generateUUID() { + return `${Date.now()}-${Math.round(Math.random() * 100)}`; +} + +module.exports = { generateUUID }; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/indexed-db.js b/devtools/client/shared/test-helpers/jest-fixtures/indexed-db.js new file mode 100644 index 0000000000..32bb957a65 --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/indexed-db.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +const store = {}; + +module.exports = { + open: () => ({}), + getItem: async key => store[key], + setItem: async (key, value) => { + store[key] = value; + }, +}; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/plural-form.js b/devtools/client/shared/test-helpers/jest-fixtures/plural-form.js new file mode 100644 index 0000000000..c6a3f6cb13 --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/plural-form.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +module.exports.PluralForm = { + get(num, str) { + return str.split(";")[1]; + }, +}; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/promise.js b/devtools/client/shared/test-helpers/jest-fixtures/promise.js new file mode 100644 index 0000000000..ad42cbd4ec --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/promise.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +module.exports = Promise; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/svgMock.js b/devtools/client/shared/test-helpers/jest-fixtures/svgMock.js new file mode 100644 index 0000000000..2c2eeed9f4 --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/svgMock.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +module.exports = "<svg></svg>"; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/telemetry.js b/devtools/client/shared/test-helpers/jest-fixtures/telemetry.js new file mode 100644 index 0000000000..45796146bf --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/telemetry.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +class Telemetry { + recordEvent() {} + start() {} + finish() {} + getKeyedHistogramById = () => ({ add: () => {} }); +} +module.exports = Telemetry; diff --git a/devtools/client/shared/test-helpers/jest-fixtures/unicode-url.js b/devtools/client/shared/test-helpers/jest-fixtures/unicode-url.js new file mode 100644 index 0000000000..a000e81cf6 --- /dev/null +++ b/devtools/client/shared/test-helpers/jest-fixtures/unicode-url.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +"use strict"; + +function getUnicodeHostname(hostname) { + return hostname; +} + +function getUnicodeUrlPath(urlPath) { + return decodeURIComponent(urlPath); +} + +function getUnicodeUrl(url) { + return decodeURIComponent(url); +} + +module.exports = { + getUnicodeHostname, + getUnicodeUrlPath, + getUnicodeUrl, +}; diff --git a/devtools/client/shared/test-helpers/shared-jest.config.js b/devtools/client/shared/test-helpers/shared-jest.config.js new file mode 100644 index 0000000000..9f72fbd9fd --- /dev/null +++ b/devtools/client/shared/test-helpers/shared-jest.config.js @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const fixturesDir = `${__dirname}/jest-fixtures`; + +module.exports = { + verbose: true, + moduleNameMapper: { + // Custom name mappers for modules that require m-c specific API. + "^devtools/shared/generate-uuid": `${fixturesDir}/generate-uuid`, + "^devtools/shared/DevToolsUtils": `${fixturesDir}/devtools-utils`, + // This is needed for the Debugger, for some reason + "shared/DevToolsUtils": `${fixturesDir}/devtools-utils`, + + // Mocks only used by node tests. + "Services-mock": `${fixturesDir}/Services`, + "ChromeUtils-mock": `${fixturesDir}/ChromeUtils`, + + "^promise": `${fixturesDir}/promise`, + "^resource://devtools/client/shared/fluent-l10n/fluent-l10n.js": `${fixturesDir}/fluent-l10n`, + "^resource://devtools/client/shared/unicode-url.js": `${fixturesDir}/unicode-url`, + // This is needed for the Debugger, for some reason + "shared/unicode-url.js": `${fixturesDir}/unicode-url`, + "shared/telemetry.js": `${fixturesDir}/telemetry`, + "^resource://devtools/client/shared/telemetry.js": `${fixturesDir}/telemetry`, + // This is needed for the Debugger, for some reason + "client/shared/telemetry$": `${fixturesDir}/telemetry`, + "devtools/shared/plural-form$": `${fixturesDir}/plural-form`, + // Sometimes returning an empty object is enough + "^resource://devtools/client/shared/link": `${fixturesDir}/empty-module`, + "^devtools/shared/flags": `${fixturesDir}/empty-module`, + "^resource://devtools/shared/indexed-db.js": `${fixturesDir}/indexed-db`, + "^devtools/shared/layout/utils": `${fixturesDir}/empty-module`, + "^devtools/client/shared/components/tree/TreeView": `${fixturesDir}/empty-module`, + // Map all require("devtools/...") to the real devtools root. + "^devtools/(.*)": `${__dirname}/../../../$1`, + "^resource://devtools/(.*)": `${__dirname}/../../../$1`, + }, +}; diff --git a/devtools/client/shared/test-helpers/shared-node-helpers.js b/devtools/client/shared/test-helpers/shared-node-helpers.js new file mode 100644 index 0000000000..ca6a728a8a --- /dev/null +++ b/devtools/client/shared/test-helpers/shared-node-helpers.js @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* global global */ + +/** + * Adds mocks for browser-environment global variables/methods to Node global. + */ +function setMocksInGlobal() { + global.Cc = new Proxy( + {}, + { + get(target, prop, receiver) { + if (prop.startsWith("@mozilla.org")) { + return { getService: () => ({}) }; + } + return null; + }, + } + ); + global.Ci = { + // sw states from + // mozilla-central/source/dom/interfaces/base/nsIServiceWorkerManager.idl + nsIServiceWorkerInfo: { + STATE_PARSED: 0, + STATE_INSTALLING: 1, + STATE_INSTALLED: 2, + STATE_ACTIVATING: 3, + STATE_ACTIVATED: 4, + STATE_REDUNDANT: 5, + STATE_UNKNOWN: 6, + }, + }; + global.Cu = { + isInAutomation: true, + now: () => {}, + }; + + global.Services = require("Services-mock"); + global.ChromeUtils = require("ChromeUtils-mock"); + + global.isWorker = false; + + global.loader = { + lazyGetter: (context, name, fn) => { + Object.defineProperty(global, name, { + get() { + delete global[name]; + global[name] = fn.apply(global); + return global[name]; + }, + configurable: true, + enumerable: true, + }); + }, + lazyRequireGetter: (context, names, module, destructure) => { + if (!Array.isArray(names)) { + names = [names]; + } + + for (const name of names) { + global.loader.lazyGetter(context, name, () => { + return destructure ? require(module)[name] : require(module || name); + }); + } + }, + lazyServiceGetter: () => {}, + }; + + global.define = function () {}; + + // Used for the HTMLTooltip component. + // And set "isSystemPrincipal: false" because can't support XUL element in node. + global.document.nodePrincipal = { + isSystemPrincipal: false, + }; + + global.requestIdleCallback = function () {}; + + global.requestAnimationFrame = function (cb) { + cb(); + return null; + }; + + // Mock getSelection + let selection; + global.getSelection = function () { + return { + toString: () => selection, + get type() { + if (selection === undefined) { + return "None"; + } + if (selection === "") { + return "Caret"; + } + return "Range"; + }, + setMockSelection: str => { + selection = str; + }, + }; + }; + + // Array#flatMap is only supported in Node 11+ + if (!Array.prototype.flatMap) { + // eslint-disable-next-line no-extend-native + Array.prototype.flatMap = function (cb) { + return this.reduce((acc, x, i, arr) => { + return acc.concat(cb(x, i, arr)); + }, []); + }; + } + + if (typeof global.TextEncoder === "undefined") { + const { TextEncoder } = require("util"); + global.TextEncoder = TextEncoder; + } + + if (typeof global.TextDecoder === "undefined") { + const { TextDecoder } = require("util"); + global.TextDecoder = TextDecoder; + } + + if (!Promise.withResolvers) { + Promise.withResolvers = function () { + let resolve, reject; + const promise = new Promise(function (res, rej) { + resolve = res; + reject = rej; + }); + return { resolve, reject, promise }; + }; + } +} + +module.exports = { + setMocksInGlobal, +}; |