Skip to content

Instantly share code, notes, and snippets.

@marcogrcr
Created March 27, 2023 19:47
Show Gist options
  • Save marcogrcr/e661120460e0e4ac39a2bf03b20d04bc to your computer and use it in GitHub Desktop.
Save marcogrcr/e661120460e0e4ac39a2bf03b20d04bc to your computer and use it in GitHub Desktop.
A simplified EventTarget polyfill for unit testing
/**
* Simplified `EventTarget` polyfill.
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
*/
class EventTarget {
/** @type {Map<string, { capture: boolean, listener: Function, once: boolean }[]>} */
_listenersMap = new Map();
/**
* @param {string} type
* @param {Function} listener
* @param {boolean | { capture?: boolean, once?: boolean } | undefined} optionsOrUseCapture
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#syntax
*/
addEventListener(type, listener, optionsOrUseCapture) {
let capture, once;
if (typeof optionsOrUseCapture === "object") {
capture = !!optionsOrUseCapture.capture;
once = !!optionsOrUseCapture.once;
} else {
capture = !!optionsOrUseCapture;
once = false;
}
this._listenersOf(type).push({ capture, listener, once });
}
/**
* @param {Event} event
* @returns {boolean}
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent#syntax
*/
dispatchEvent(event) {
const currentTarget = Object.getOwnPropertyDescriptor(event, "currentTarget");
const eventPhase = Object.getOwnPropertyDescriptor(event, "eventPhase");
const target = Object.getOwnPropertyDescriptor(event, "target");
try {
Object.defineProperty(event, "currentTarget", { ...currentTarget, value: this });
Object.defineProperty(event, "eventPhase", { ...eventPhase, value: Event.AT_TARGET });
Object.defineProperty(event, "target", { ...target, value: this });
const listeners = this._listenersOf(event.type);
const listenersToRemove = [];
listeners.forEach((l, i) => {
try {
l.listener.call(this, event);
} catch (e) {
console.error(e);
}
if (l.once) {
listenersToRemove.push(i);
}
})
listenersToRemove.reverse().forEach((i) => {
listeners.splice(i, 1);
});
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent#return_value
return !event.cancelable || !event.defaultPrevented;
} finally {
Object.defineProperty(event, "currentTarget", currentTarget);
Object.defineProperty(event, "eventPhase", eventPhase);
Object.defineProperty(event, "target", target);
}
}
/**
* @param {string} type
* @param {Function} listener
* @param {boolean | { capture?: boolean } | undefined} optionsOrUseCapture
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener#syntax
*/
removeEventListener(type, listener, optionsOrUseCapture) {
const capture = typeof optionsOrUseCapture === "object" ? !!optionsOrUseCapture.capture : !!optionsOrUseCapture;
const listeners = this._listenersOf(type);
const index = listeners.findIndex((l) => l.capture === capture && l.listener === listener);
if (index >= 0) {
listeners.splice(index, 1);
}
}
/** @returns {{ capture: boolean, listener: Function, once: boolean }[]} */
_listenersOf(type) {
let listeners = this._listenersMap.get(type);
if (!listeners) {
listeners = [];
this._listenersMap.set(type, listeners);
}
return listeners;
}
}
window.EventTarget = EventTarget;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment