UNPKG

mock-xmlhttprequest

Version:
150 lines (146 loc) 6.01 kB
/** * mock-xmlhttprequest v8.4.1 * (c) 2025 Bertrand Guay-Paquet * @license MIT */ 'use strict'; /** * Implementation of XMLHttpRequestEventTarget. A target for dispatching events. * * See https://xhr.spec.whatwg.org/#xmlhttprequesteventtarget */ class XhrEventTarget { constructor(eventContext) { this._eventContext = eventContext ?? this; this._listeners = new Map(); } get onabort() { return this._getEventHandlerProperty('abort'); } set onabort(value) { this._setEventHandlerProperty('abort', value); } get onerror() { return this._getEventHandlerProperty('error'); } set onerror(value) { this._setEventHandlerProperty('error', value); } get onload() { return this._getEventHandlerProperty('load'); } set onload(value) { this._setEventHandlerProperty('load', value); } get onloadend() { return this._getEventHandlerProperty('loadend'); } set onloadend(value) { this._setEventHandlerProperty('loadend', value); } get onloadstart() { return this._getEventHandlerProperty('loadstart'); } set onloadstart(value) { this._setEventHandlerProperty('loadstart', value); } get onprogress() { return this._getEventHandlerProperty('progress'); } set onprogress(value) { this._setEventHandlerProperty('progress', value); } get ontimeout() { return this._getEventHandlerProperty('timeout'); } set ontimeout(value) { this._setEventHandlerProperty('timeout', value); } /** * @returns Whether any event listener is registered */ hasListeners() { return [...this._listeners.values()].some((listeners) => listeners.length > 0); } addEventListener(type, listener, options) { if (listener) { const listenerEntry = makeListenerEntry(listener, false, options); const listeners = this._listeners.get(type) ?? []; // If eventTarget’s event listener list does not contain an event listener whose type is // listener’s type, callback is listener’s callback, and capture is listener’s capture, then // append listener to eventTarget’s event listener list. // See https://dom.spec.whatwg.org/#add-an-event-listener if (listeners.every(({ isEventHandlerProperty, listener, useCapture }) => { return isEventHandlerProperty || listenerEntry.listener !== listener || listenerEntry.useCapture !== useCapture; })) { listeners.push(listenerEntry); this._listeners.set(type, listeners); } } } removeEventListener(type, listener, options) { if (listener) { const listeners = this._listeners.get(type); if (listeners) { const listenerEntry = makeListenerEntry(listener, false, options); const index = listeners.findIndex(({ isEventHandlerProperty, listener, useCapture }) => { return !isEventHandlerProperty && listenerEntry.listener === listener && listenerEntry.useCapture === useCapture; }); if (index >= 0) { listeners[index].removed = true; listeners.splice(index, 1); } } } } /** * Calls all the listeners for the event. * * @param event Event * @returns Always true since none of the xhr event are cancelable */ dispatchEvent(event) { // Only the event listeners registered at this point should be called. Storing them here avoids // problems with callbacks that add or remove listeners. const listeners = this._listeners.get(event.type); if (listeners) { [...listeners].forEach((listenerEntry) => { if (!listenerEntry.removed) { if (listenerEntry.once) { const index = listeners.indexOf(listenerEntry); if (index >= 0) { listeners.splice(index, 1); } } if (typeof listenerEntry.listener === 'function') { listenerEntry.listener.call(this._eventContext, event); } else { listenerEntry.listener.handleEvent(event); } } }); } return true; } _getEventHandlerProperty(event) { const listeners = this._listeners.get(event); if (listeners) { const entry = listeners.find((entry) => entry.isEventHandlerProperty); if (entry) { return entry.listener; } } return null; } _setEventHandlerProperty(event, value) { const listeners = this._listeners.get(event); if (listeners) { const index = listeners.findIndex((entry) => entry.isEventHandlerProperty); if (index >= 0) { if (listeners[index].listener === value) { // no change return; } listeners[index].removed = true; listeners.splice(index, 1); } } if (value) { const listenerEntry = makeListenerEntry(value, true); if (listeners) { listeners.push(listenerEntry); } else { this._listeners.set(event, [listenerEntry]); } } } } function makeListenerEntry(listener, isEventHandlerProperty, options) { const optionsIsBoolean = typeof options === 'boolean'; return { listener, isEventHandlerProperty, useCapture: optionsIsBoolean ? options : !!options?.capture, once: optionsIsBoolean ? false : !!options?.once, removed: false, }; } module.exports = XhrEventTarget;