// Copyright 2017-2026 @pezkuwi/dev-test authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { DOMWindow } from 'jsdom'; import { JSDOM } from 'jsdom'; /** * Constructors that Node defines as well. A DOM node only accepts an event * created in its own realm, so in the browser environment the JSDOM ones * replace Node's, as they do under jest-environment-jsdom. * * WebSocket follows from that: Node's own dispatches its events through * Node's EventTarget, which rejects the JSDOM Event that replaced Node's, so * no open, message or error ever reaches a listener and a connection never * comes up. JSDOM's WebSocket dispatches events from its own realm. */ export const BROWSER_OVERRIDES: readonly string[] = ['CustomEvent', 'Event', 'WebSocket']; /** * JSDOM does no layout, so no element ever changes size and the callback * never runs. JSDOM has no ResizeObserver of its own. */ class ResizeObserver { observe (): void { // nothing is ever resized } unobserve (): void { // nothing is being watched } disconnect (): void { // nothing is being watched } } /** The DOM node hierarchy, which libraries check with instanceof */ function nodes (window: DOMWindow) { return { CharacterData: window.CharacterData, Comment: window.Comment, Document: window.Document, DocumentFragment: window.DocumentFragment, Element: window.Element, Node: window.Node, NodeList: window.NodeList, SVGElement: window.SVGElement, ShadowRoot: window.ShadowRoot, Text: window.Text }; } /** Event constructors. Node has its own Event and CustomEvent, see BROWSER_OVERRIDES */ function events (window: DOMWindow) { return { CompositionEvent: window.CompositionEvent, CustomEvent: window.CustomEvent, Event: window.Event, FocusEvent: window.FocusEvent, InputEvent: window.InputEvent, KeyboardEvent: window.KeyboardEvent, MouseEvent: window.MouseEvent, TouchEvent: window.TouchEvent, UIEvent: window.UIEvent, WheelEvent: window.WheelEvent }; } /** Network, see BROWSER_OVERRIDES for why WebSocket is JSDOM's */ function network (window: DOMWindow) { return { WebSocket: window.WebSocket }; } /** Styles, geometry and the observers */ function layout (window: DOMWindow) { return { // present on the JSDOM window, absent from its DOMWindow type DOMRect: window['DOMRect'] as typeof DOMRect, DOMRectReadOnly: window['DOMRectReadOnly'] as typeof DOMRectReadOnly, MutationObserver: window.MutationObserver, MutationRecord: window.MutationRecord, ResizeObserver, getComputedStyle: window.getComputedStyle.bind(window) }; } /** * Export a very basic JSDom environment - this is just enough so we have * @testing-environment/react tests passing in this repo * * FIXME: This approach is actually _explicitly_ discouraged by JSDOM - when * using window you should run the tests inside that context, instead of just * blindly relying on the globals as we do here */ export function browser () { const { window } = new JSDOM('', { url: 'http://localhost' }); return { ...nodes(window), ...events(window), ...layout(window), ...network(window), // All HTML Elements that are defined on the JSDOM window object. // (we copied as-is from the types definition). We cannot get this // via Object.keys(window).filter(...) so we have to specify explicitly HTMLAnchorElement: window.HTMLAnchorElement, HTMLAreaElement: window.HTMLAreaElement, HTMLAudioElement: window.HTMLAudioElement, HTMLBRElement: window.HTMLBRElement, HTMLBaseElement: window.HTMLBaseElement, HTMLBodyElement: window.HTMLBodyElement, HTMLButtonElement: window.HTMLButtonElement, HTMLCanvasElement: window.HTMLCanvasElement, HTMLDListElement: window.HTMLDListElement, HTMLDataElement: window.HTMLDataElement, HTMLDataListElement: window.HTMLDataListElement, HTMLDetailsElement: window.HTMLDetailsElement, HTMLDialogElement: window.HTMLDialogElement, HTMLDirectoryElement: window.HTMLDirectoryElement, HTMLDivElement: window.HTMLDivElement, HTMLElement: window.HTMLElement, HTMLEmbedElement: window.HTMLEmbedElement, HTMLFieldSetElement: window.HTMLFieldSetElement, HTMLFontElement: window.HTMLFontElement, HTMLFormElement: window.HTMLFormElement, HTMLFrameElement: window.HTMLFrameElement, HTMLFrameSetElement: window.HTMLFrameSetElement, HTMLHRElement: window.HTMLHRElement, HTMLHeadElement: window.HTMLHeadElement, HTMLHeadingElement: window.HTMLHeadingElement, HTMLHtmlElement: window.HTMLHtmlElement, HTMLIFrameElement: window.HTMLIFrameElement, HTMLImageElement: window.HTMLImageElement, HTMLInputElement: window.HTMLInputElement, HTMLLIElement: window.HTMLLIElement, HTMLLabelElement: window.HTMLLabelElement, HTMLLegendElement: window.HTMLLegendElement, HTMLLinkElement: window.HTMLLinkElement, HTMLMapElement: window.HTMLMapElement, HTMLMarqueeElement: window.HTMLMarqueeElement, HTMLMediaElement: window.HTMLMediaElement, HTMLMenuElement: window.HTMLMenuElement, HTMLMetaElement: window.HTMLMetaElement, HTMLMeterElement: window.HTMLMeterElement, HTMLModElement: window.HTMLModElement, HTMLOListElement: window.HTMLOListElement, HTMLObjectElement: window.HTMLObjectElement, HTMLOptGroupElement: window.HTMLOptGroupElement, HTMLOptionElement: window.HTMLOptionElement, HTMLOutputElement: window.HTMLOutputElement, HTMLParagraphElement: window.HTMLParagraphElement, HTMLParamElement: window.HTMLParamElement, HTMLPictureElement: window.HTMLPictureElement, HTMLPreElement: window.HTMLPreElement, HTMLProgressElement: window.HTMLProgressElement, HTMLQuoteElement: window.HTMLQuoteElement, HTMLScriptElement: window.HTMLScriptElement, HTMLSelectElement: window.HTMLSelectElement, HTMLSlotElement: window.HTMLSlotElement, HTMLSourceElement: window.HTMLSourceElement, HTMLSpanElement: window.HTMLSpanElement, HTMLStyleElement: window.HTMLStyleElement, HTMLTableCaptionElement: window.HTMLTableCaptionElement, HTMLTableCellElement: window.HTMLTableCellElement, HTMLTableColElement: window.HTMLTableColElement, HTMLTableElement: window.HTMLTableElement, HTMLTableRowElement: window.HTMLTableRowElement, HTMLTableSectionElement: window.HTMLTableSectionElement, HTMLTemplateElement: window.HTMLTemplateElement, HTMLTextAreaElement: window.HTMLTextAreaElement, HTMLTimeElement: window.HTMLTimeElement, HTMLTitleElement: window.HTMLTitleElement, HTMLTrackElement: window.HTMLTrackElement, HTMLUListElement: window.HTMLUListElement, HTMLUnknownElement: window.HTMLUnknownElement, HTMLVideoElement: window.HTMLVideoElement, // normal service resumes, the base top-level names crypto: window.crypto, document: window.document, localStorage: window.localStorage, navigator: window.navigator, // window... window }; }