@inrupt/jest-jsdom-polyfills
Version:
This package provides various polyfills needed on jest/jsdom when using the Inrupt SDKs
128 lines (118 loc) • 5.38 kB
JavaScript
// Copyright Inrupt Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
// Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
/* eslint-disable @typescript-eslint/no-require-imports */
// TextEncoder / TextDecoder APIs are used by Jose, but are not provided by
// jsdom, all node versions supported provide these via the util module
if (
typeof globalThis.TextEncoder === "undefined" ||
typeof globalThis.TextDecoder === "undefined"
) {
const utils = require("util");
globalThis.TextEncoder = utils.TextEncoder;
globalThis.TextDecoder = utils.TextDecoder;
// TextEncoder references a Uint8Array constructor different than the global
// one used by users in tests. The following enforces the same constructor to
// be referenced by both.
// FIXME: currently this doesn't work, and must be set in a custom environment.
globalThis.Uint8Array = Uint8Array;
}
if (
typeof globalThis.crypto !== "undefined" &&
// jsdom doesn't implement the subtle Web Crypto API
typeof globalThis.crypto.subtle === "undefined"
) {
// Requires OPENSSL_CONF=/dev/null (see https://github.com/nodejs/node/discussions/43184)
const {
Crypto: WCrypto,
CryptoKey: WCryptoKey,
} = require("@peculiar/webcrypto");
const wcrypto = new WCrypto();
// We can't use `Object.assign(globalThis.crypto, wcrypto)` here: jsdom's
// Crypto exposes some properties (e.g. `Symbol.toStringTag`) as read-only,
// and @peculiar/webcrypto carries the same keys as own, enumerable properties.
// Object.assign would attempt to write them onto the read-only target and
// throw (see https://github.com/inrupt/typescript-sdk-tools/issues). Instead,
// copy each key individually, skipping any that can't be written on the
// target. In practice the only property we actually need to polyfill is
// `subtle`, which jsdom does not implement.
for (const key of Reflect.ownKeys(wcrypto)) {
// jsdom defines some properties (e.g. `Symbol.toStringTag`) on
// `Crypto.prototype` rather than on the instance, so an own-property
// lookup misses them. Walk the prototype chain to find the descriptor
// that an assignment would actually hit.
let holder = globalThis.crypto;
let descriptor;
while (
holder &&
!(descriptor = Object.getOwnPropertyDescriptor(holder, key))
) {
holder = Object.getPrototypeOf(holder);
}
// Skip anything we cannot assign through `[[Set]]`: read-only data
// properties and accessors without a setter.
if (
descriptor &&
(descriptor.writable === false || (descriptor.get && !descriptor.set))
) {
continue;
}
globalThis.crypto[key] = wcrypto[key];
}
globalThis.CryptoKey = WCryptoKey;
}
// jsdom ships its own Blob/File globals, but they are incomplete: their
// prototypes only expose `slice`/`size`/`type` and are missing `text()`,
// `arrayBuffer()` and `stream()`. Worse, undici's `Response.blob()`
// constructs its result from `globalThis.Blob`, so the broken prototype
// leaks into anything that reads a response body as a blob. We therefore
// replace any Blob/File that lacks `text()` with Node's native, spec-compliant
// implementations from `node:buffer` (available on all supported Node versions).
const { Blob: NodeBlob, File: NodeFile } = require("node:buffer");
if (
typeof globalThis.Blob === "undefined" ||
typeof globalThis.Blob.prototype.text !== "function"
) {
globalThis.Blob = NodeBlob;
}
if (
typeof globalThis.File === "undefined" ||
typeof globalThis.File.prototype.text !== "function"
) {
globalThis.File = NodeFile;
}
// FIXME This is a temporary workaround for https://github.com/jsdom/jsdom/issues/1724#issuecomment-720727999
// The following fetch APIs are missing in JSDom
if (
typeof globalThis.Response === "undefined" ||
typeof globalThis.Request === "undefined" ||
typeof globalThis.Headers === "undefined" ||
typeof globalThis.fetch === "undefined"
) {
// ReadableStream and MessagePort are required by undici,
// and are not available in JSDom
const { ReadableStream } = require("node:stream/web");
globalThis.ReadableStream = ReadableStream;
globalThis.MessagePort = require("worker_threads").MessagePort;
const undici = require("undici");
globalThis.Response = undici.Response;
globalThis.Request = undici.Request;
globalThis.Headers = undici.Headers;
globalThis.fetch = undici.fetch;
}