summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/test-helpers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/client/shared/test-helpers
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/ChromeUtils.js12
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/Services.js566
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/devtools-utils.js13
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/empty-module.js7
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/fluent-l10n.js23
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/generate-uuid.js11
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/indexed-db.js15
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/plural-form.js11
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/promise.js7
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/svgMock.js7
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/telemetry.js13
-rw-r--r--devtools/client/shared/test-helpers/jest-fixtures/unicode-url.js23
-rw-r--r--devtools/client/shared/test-helpers/shared-jest.config.js42
-rw-r--r--devtools/client/shared/test-helpers/shared-node-helpers.js142
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,
+};