import { URL } from 'node:url'; import { Console } from 'node:console'; // SEE https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/interfaces.js const LIVING_KEYS = [ "DOMException", "EventTarget", "NamedNodeMap", "Node", "Attr", "Element", "DocumentFragment", "DOMImplementation", "Document", "XMLDocument", "CharacterData", "Text", "CDATASection", "ProcessingInstruction", "Comment", "DocumentType", "NodeList", "RadioNodeList", "HTMLCollection", "HTMLOptionsCollection", "DOMStringMap", "DOMTokenList", "StyleSheetList", "HTMLElement", "HTMLHeadElement", "HTMLTitleElement", "HTMLBaseElement", "HTMLLinkElement", "HTMLMetaElement", "HTMLStyleElement", "HTMLBodyElement", "HTMLHeadingElement", "HTMLParagraphElement", "HTMLHRElement", "HTMLPreElement", "HTMLUListElement", "HTMLOListElement", "HTMLLIElement", "HTMLMenuElement", "HTMLDListElement", "HTMLDivElement", "HTMLAnchorElement", "HTMLAreaElement", "HTMLBRElement", "HTMLButtonElement", "HTMLCanvasElement", "HTMLDataElement", "HTMLDataListElement", "HTMLDetailsElement", "HTMLDialogElement", "HTMLDirectoryElement", "HTMLFieldSetElement", "HTMLFontElement", "HTMLFormElement", "HTMLHtmlElement", "HTMLImageElement", "HTMLInputElement", "HTMLLabelElement", "HTMLLegendElement", "HTMLMapElement", "HTMLMarqueeElement", "HTMLMediaElement", "HTMLMeterElement", "HTMLModElement", "HTMLOptGroupElement", "HTMLOptionElement", "HTMLOutputElement", "HTMLPictureElement", "HTMLProgressElement", "HTMLQuoteElement", "HTMLScriptElement", "HTMLSelectElement", "HTMLSlotElement", "HTMLSourceElement", "HTMLSpanElement", "HTMLTableCaptionElement", "HTMLTableCellElement", "HTMLTableColElement", "HTMLTableElement", "HTMLTimeElement", "HTMLTableRowElement", "HTMLTableSectionElement", "HTMLTemplateElement", "HTMLTextAreaElement", "HTMLUnknownElement", "HTMLFrameElement", "HTMLFrameSetElement", "HTMLIFrameElement", "HTMLEmbedElement", "HTMLObjectElement", "HTMLParamElement", "HTMLVideoElement", "HTMLAudioElement", "HTMLTrackElement", "HTMLFormControlsCollection", "SVGElement", "SVGGraphicsElement", "SVGSVGElement", "SVGTitleElement", "SVGAnimatedString", "SVGNumber", "SVGStringList", "Event", "CloseEvent", "CustomEvent", "MessageEvent", "ErrorEvent", "HashChangeEvent", "PopStateEvent", "StorageEvent", "ProgressEvent", "PageTransitionEvent", "SubmitEvent", "UIEvent", "FocusEvent", "InputEvent", "MouseEvent", "KeyboardEvent", "TouchEvent", "CompositionEvent", "WheelEvent", "BarProp", "External", "Location", "History", "Screen", "Crypto", "Performance", "Navigator", "PluginArray", "MimeTypeArray", "Plugin", "MimeType", "FileReader", "FormData", "Blob", "File", "FileList", "ValidityState", "DOMParser", "XMLSerializer", "XMLHttpRequestEventTarget", "XMLHttpRequestUpload", "XMLHttpRequest", "WebSocket", "NodeFilter", "NodeIterator", "TreeWalker", "AbstractRange", "Range", "StaticRange", "Selection", "Storage", "CustomElementRegistry", "ShadowRoot", "MutationObserver", "MutationRecord", "Uint8Array", "Uint16Array", "Uint32Array", "Uint8ClampedArray", "Int8Array", "Int16Array", "Int32Array", "Float32Array", "Float64Array", "ArrayBuffer", "DOMRectReadOnly", "DOMRect", "Image", "Audio", "Option", "CSS" ]; const OTHER_KEYS = [ "addEventListener", "alert", "blur", "cancelAnimationFrame", "close", "confirm", "createPopup", "dispatchEvent", "document", "focus", "frames", "getComputedStyle", "history", "innerHeight", "innerWidth", "length", "location", "matchMedia", "moveBy", "moveTo", "name", "navigator", "open", "outerHeight", "outerWidth", "pageXOffset", "pageYOffset", "parent", "postMessage", "print", "prompt", "removeEventListener", "requestAnimationFrame", "resizeBy", "resizeTo", "screen", "screenLeft", "screenTop", "screenX", "screenY", "scroll", "scrollBy", "scrollLeft", "scrollTo", "scrollTop", "scrollX", "scrollY", "self", "stop", "top", "Window", "window" ]; const KEYS = LIVING_KEYS.concat(OTHER_KEYS); const skipKeys = [ "window", "self", "top", "parent" ]; function getWindowKeys(global, win, additionalKeys = []) { const keysArray = [...additionalKeys, ...KEYS]; return new Set(keysArray.concat(Object.getOwnPropertyNames(win)).filter((k) => { if (skipKeys.includes(k)) return false; if (k in global) return keysArray.includes(k); return true; })); } function isClassLikeName(name) { return name[0] === name[0].toUpperCase(); } function populateGlobal(global, win, options = {}) { const { bindFunctions = false } = options; const keys = getWindowKeys(global, win, options.additionalKeys); const originals = /* @__PURE__ */ new Map(); const overridenKeys = new Set([...KEYS, ...options.additionalKeys || []]); const overrideObject = /* @__PURE__ */ new Map(); for (const key of keys) { const boundFunction = bindFunctions && typeof win[key] === "function" && !isClassLikeName(key) && win[key].bind(win); if (overridenKeys.has(key) && key in global) originals.set(key, global[key]); Object.defineProperty(global, key, { get() { if (overrideObject.has(key)) return overrideObject.get(key); if (boundFunction) return boundFunction; return win[key]; }, set(v) { overrideObject.set(key, v); }, configurable: true }); } global.window = global; global.self = global; global.top = global; global.parent = global; if (global.global) global.global = global; // rewrite defaultView to reference the same global context if (global.document && global.document.defaultView) Object.defineProperty(global.document, "defaultView", { get: () => global, enumerable: true, configurable: true }); skipKeys.forEach((k) => keys.add(k)); return { keys, skipKeys, originals }; } var edge = { name: "edge-runtime", viteEnvironment: "ssr", async setupVM() { const { EdgeVM } = await import('@edge-runtime/vm'); const vm = new EdgeVM({ extend: (context) => { context.global = context; context.Buffer = Buffer; return context; } }); return { getVmContext() { return vm.context; }, teardown() { // nothing to teardown } }; }, async setup(global) { const { EdgeVM } = await import('@edge-runtime/vm'); const { keys, originals } = populateGlobal(global, new EdgeVM({ extend: (context) => { context.global = context; context.Buffer = Buffer; KEYS.forEach((key) => { if (key in global) context[key] = global[key]; }); return context; } }).context, { bindFunctions: true }); return { teardown(global) { keys.forEach((key) => delete global[key]); originals.forEach((v, k) => global[k] = v); } }; } }; async function teardownWindow(win) { if (win.close && win.happyDOM.abort) { await win.happyDOM.abort(); win.close(); } else win.happyDOM.cancelAsync(); } var happy = { name: "happy-dom", viteEnvironment: "client", async setupVM({ happyDOM = {} }) { const { Window } = await import('happy-dom'); let win = new Window({ ...happyDOM, console: console && globalThis.console ? globalThis.console : void 0, url: happyDOM.url || "http://localhost:3000", settings: { ...happyDOM.settings, disableErrorCapturing: true } }); // TODO: browser doesn't expose Buffer, but a lot of dependencies use it win.Buffer = Buffer; // inject structuredClone if it exists if (typeof structuredClone !== "undefined" && !win.structuredClone) win.structuredClone = structuredClone; return { getVmContext() { return win; }, async teardown() { await teardownWindow(win); win = void 0; } }; }, async setup(global, { happyDOM = {} }) { // happy-dom v3 introduced a breaking change to Window, but // provides GlobalWindow as a way to use previous behaviour const { Window, GlobalWindow } = await import('happy-dom'); const win = new (GlobalWindow || Window)({ ...happyDOM, console: console && global.console ? global.console : void 0, url: happyDOM.url || "http://localhost:3000", settings: { ...happyDOM.settings, disableErrorCapturing: true } }); const { keys, originals } = populateGlobal(global, win, { bindFunctions: true, additionalKeys: [ "Request", "Response", "MessagePort", "fetch", "Headers", "AbortController", "AbortSignal", "URL", "URLSearchParams", "FormData" ] }); return { async teardown(global) { await teardownWindow(win); keys.forEach((key) => delete global[key]); originals.forEach((v, k) => global[k] = v); } }; } }; function catchWindowErrors(window) { let userErrorListenerCount = 0; function throwUnhandlerError(e) { if (userErrorListenerCount === 0 && e.error != null) { e.preventDefault(); process.emit("uncaughtException", e.error); } } const addEventListener = window.addEventListener.bind(window); const removeEventListener = window.removeEventListener.bind(window); window.addEventListener("error", throwUnhandlerError); window.addEventListener = function(...args) { if (args[0] === "error") userErrorListenerCount++; return addEventListener.apply(this, args); }; window.removeEventListener = function(...args) { if (args[0] === "error" && userErrorListenerCount) userErrorListenerCount--; return removeEventListener.apply(this, args); }; return function clearErrorHandlers() { window.removeEventListener("error", throwUnhandlerError); }; } let NodeFormData_; let NodeBlob_; let NodeRequest_; var jsdom = { name: "jsdom", viteEnvironment: "client", async setupVM({ jsdom = {} }) { // delay initialization because it takes ~1s NodeFormData_ = globalThis.FormData; NodeBlob_ = globalThis.Blob; NodeRequest_ = globalThis.Request; const { CookieJar, JSDOM, ResourceLoader, VirtualConsole } = await import('jsdom'); const { html = "", userAgent, url = "http://localhost:3000", contentType = "text/html", pretendToBeVisual = true, includeNodeLocations = false, runScripts = "dangerously", resources, console = false, cookieJar = false, ...restOptions } = jsdom; let virtualConsole; if (console && globalThis.console) { virtualConsole = new VirtualConsole(); // jsdom <27 if ("sendTo" in virtualConsole) virtualConsole.sendTo(globalThis.console); else virtualConsole.forwardTo(globalThis.console); } let dom = new JSDOM(html, { pretendToBeVisual, resources: resources ?? (userAgent ? new ResourceLoader({ userAgent }) : void 0), runScripts, url, virtualConsole, cookieJar: cookieJar ? new CookieJar() : void 0, includeNodeLocations, contentType, userAgent, ...restOptions }); const clearAddEventListenerPatch = patchAddEventListener(dom.window); const clearWindowErrors = catchWindowErrors(dom.window); const utils = createCompatUtils(dom.window); // TODO: browser doesn't expose Buffer, but a lot of dependencies use it dom.window.Buffer = Buffer; dom.window.jsdom = dom; dom.window.Request = createCompatRequest(utils); dom.window.URL = createJSDOMCompatURL(utils); for (const name of [ "structuredClone", "BroadcastChannel", "MessageChannel", "MessagePort", "TextEncoder", "TextDecoder" ]) { const value = globalThis[name]; if (typeof value !== "undefined" && typeof dom.window[name] === "undefined") dom.window[name] = value; } for (const name of [ "fetch", "Response", "Headers", "AbortController", "AbortSignal", "URLSearchParams" ]) { const value = globalThis[name]; if (typeof value !== "undefined") dom.window[name] = value; } return { getVmContext() { return dom.getInternalVMContext(); }, teardown() { clearAddEventListenerPatch(); clearWindowErrors(); dom.window.close(); dom = void 0; } }; }, async setup(global, { jsdom = {} }) { // delay initialization because it takes ~1s NodeFormData_ = globalThis.FormData; NodeBlob_ = globalThis.Blob; NodeRequest_ = globalThis.Request; const { CookieJar, JSDOM, ResourceLoader, VirtualConsole } = await import('jsdom'); const { html = "", userAgent, url = "http://localhost:3000", contentType = "text/html", pretendToBeVisual = true, includeNodeLocations = false, runScripts = "dangerously", resources, console = false, cookieJar = false, ...restOptions } = jsdom; let virtualConsole; if (console && globalThis.console) { virtualConsole = new VirtualConsole(); // jsdom <27 if ("sendTo" in virtualConsole) virtualConsole.sendTo(globalThis.console); else virtualConsole.forwardTo(globalThis.console); } const dom = new JSDOM(html, { pretendToBeVisual, resources: resources ?? (userAgent ? new ResourceLoader({ userAgent }) : void 0), runScripts, url, virtualConsole, cookieJar: cookieJar ? new CookieJar() : void 0, includeNodeLocations, contentType, userAgent, ...restOptions }); const clearAddEventListenerPatch = patchAddEventListener(dom.window); const { keys, originals } = populateGlobal(global, dom.window, { bindFunctions: true }); const clearWindowErrors = catchWindowErrors(global); const utils = createCompatUtils(dom.window); global.jsdom = dom; global.Request = createCompatRequest(utils); global.URL = createJSDOMCompatURL(utils); return { teardown(global) { clearAddEventListenerPatch(); clearWindowErrors(); dom.window.close(); delete global.jsdom; keys.forEach((key) => delete global[key]); originals.forEach((v, k) => global[k] = v); } }; } }; function createCompatRequest(utils) { return class Request extends NodeRequest_ { constructor(...args) { const [input, init] = args; if (init?.body != null) { const compatInit = { ...init }; if (init.body instanceof utils.window.Blob) compatInit.body = utils.makeCompatBlob(init.body); if (init.body instanceof utils.window.FormData) compatInit.body = utils.makeCompatFormData(init.body); super(input, compatInit); } else super(...args); } static [Symbol.hasInstance](instance) { return instance instanceof NodeRequest_; } }; } function createJSDOMCompatURL(utils) { return class URL$1 extends URL { static createObjectURL(blob) { if (blob instanceof utils.window.Blob) { const compatBlob = utils.makeCompatBlob(blob); return URL.createObjectURL(compatBlob); } return URL.createObjectURL(blob); } static [Symbol.hasInstance](instance) { return instance instanceof URL; } }; } function createCompatUtils(window) { // this returns a hidden Symbol(impl) // this is cursed, and jsdom should just implement fetch API itself const implSymbol = Object.getOwnPropertySymbols(Object.getOwnPropertyDescriptors(new window.Blob()))[0]; const utils = { window, makeCompatFormData(formData) { const nodeFormData = new NodeFormData_(); formData.forEach((value, key) => { if (value instanceof window.Blob) nodeFormData.append(key, utils.makeCompatBlob(value)); else nodeFormData.append(key, value); }); return nodeFormData; }, makeCompatBlob(blob) { const buffer = blob[implSymbol]._buffer; return new NodeBlob_([buffer], { type: blob.type }); } }; return utils; } function patchAddEventListener(window) { const abortControllers = /* @__PURE__ */ new WeakMap(); const JSDOMAbortSignal = window.AbortSignal; const JSDOMAbortController = window.AbortController; const originalAddEventListener = window.EventTarget.prototype.addEventListener; function getJsdomAbortController(signal) { if (!abortControllers.has(signal)) { const jsdomAbortController = new JSDOMAbortController(); signal.addEventListener("abort", () => { jsdomAbortController.abort(signal.reason); }); abortControllers.set(signal, jsdomAbortController); } return abortControllers.get(signal); } window.EventTarget.prototype.addEventListener = function addEventListener(type, callback, options) { if (typeof options === "object" && options.signal != null) { const { signal, ...otherOptions } = options; // - this happens because AbortSignal is provided by Node.js, // but jsdom APIs require jsdom's AbortSignal, while Node APIs // (like fetch and Request) require a Node.js AbortSignal // - disable narrow typing with "as any" because we need it later if (!(signal instanceof JSDOMAbortSignal)) { const jsdomCompatOptions = Object.create(null); Object.assign(jsdomCompatOptions, otherOptions); jsdomCompatOptions.signal = getJsdomAbortController(signal).signal; return originalAddEventListener.call(this, type, callback, jsdomCompatOptions); } } return originalAddEventListener.call(this, type, callback, options); }; return () => { window.EventTarget.prototype.addEventListener = originalAddEventListener; }; } // some globals we do not want, either because deprecated or we set it ourselves const denyList = new Set([ "GLOBAL", "root", "global", "Buffer", "ArrayBuffer", "Uint8Array" ]); const nodeGlobals = /* @__PURE__ */ new Map(); function populateNodeGlobals() { if (nodeGlobals.size !== 0) return; const names = Object.getOwnPropertyNames(globalThis); const length = names.length; for (let i = 0; i < length; i++) { const globalName = names[i]; if (!denyList.has(globalName)) { const descriptor = Object.getOwnPropertyDescriptor(globalThis, globalName); if (!descriptor) throw new Error(`No property descriptor for ${globalName}, this is a bug in Vitest.`); nodeGlobals.set(globalName, descriptor); } } } var node = { name: "node", viteEnvironment: "ssr", async setupVM() { populateNodeGlobals(); const vm = await import('node:vm'); let context = vm.createContext(); let global = vm.runInContext("this", context); const contextGlobals = new Set(Object.getOwnPropertyNames(global)); for (const [nodeGlobalsKey, descriptor] of nodeGlobals) if (!contextGlobals.has(nodeGlobalsKey)) if (descriptor.configurable) Object.defineProperty(global, nodeGlobalsKey, { configurable: true, enumerable: descriptor.enumerable, get() { // @ts-expect-error: no index signature const val = globalThis[nodeGlobalsKey]; // override lazy getter Object.defineProperty(global, nodeGlobalsKey, { configurable: true, enumerable: descriptor.enumerable, value: val, writable: descriptor.writable === true || nodeGlobalsKey === "performance" }); return val; }, set(val) { // override lazy getter Object.defineProperty(global, nodeGlobalsKey, { configurable: true, enumerable: descriptor.enumerable, value: val, writable: true }); } }); else if ("value" in descriptor) Object.defineProperty(global, nodeGlobalsKey, { configurable: false, enumerable: descriptor.enumerable, value: descriptor.value, writable: descriptor.writable }); else Object.defineProperty(global, nodeGlobalsKey, { configurable: false, enumerable: descriptor.enumerable, get: descriptor.get, set: descriptor.set }); global.global = global; global.Buffer = Buffer; global.ArrayBuffer = ArrayBuffer; // TextEncoder (global or via 'util') references a Uint8Array constructor // different than the global one used by users in tests. This makes sure the // same constructor is referenced by both. global.Uint8Array = Uint8Array; return { getVmContext() { return context; }, teardown() { context = void 0; global = void 0; } }; }, async setup(global) { global.console.Console = Console; return { teardown(global) { delete global.console.Console; } }; } }; const environments = { node, jsdom, "happy-dom": happy, "edge-runtime": edge }; export { environments as e, populateGlobal as p };