UNPKG

@ngneat/spectator

Version:

A powerful tool to simplify your Angular tests

61 lines 8.67 kB
import { dispatchFakeEvent } from '../dispatch-events'; import { isRunningInJsDom } from '../utils'; /** Property added to HTML Elements to ensure we don't double-patch focus methods on an element. */ const IS_FOCUS_PATCHED_PROP = Symbol('isFocusPatched'); /** Ensures that a single set of matching focus and blur events occur when HTMLElement.focus() is called. */ class FocusEventWatcher { constructor(element) { this.element = element; /** Set to true when browser sends a blur event for priorActiveElement */ this._blurred = false; /** Set to true when browser sends a focus event for element */ this._focused = false; this.element.addEventListener('focus', this); this.priorActiveElement = element.ownerDocument.activeElement; this.priorActiveElement?.addEventListener('blur', this); } handleEvent({ type }) { if (type === 'focus') { this._focused = true; } else if (type === 'blur') { this._blurred = true; } } /** * If focus and blur events haven't occurred, fire fake ones. */ ensureFocusEvents() { this.element.removeEventListener('focus', this); this.priorActiveElement?.removeEventListener('blur', this); // Ensure priorActiveElement is blurred if (!this._blurred && this.priorActiveElement) { dispatchFakeEvent(this.priorActiveElement, 'blur'); } if (!this._focused) { dispatchFakeEvent(this.element, 'focus'); // Needed to cause focus event } } } /** * Patches an element's focus and blur methods to emit events consistently and predictably in tests. * This is necessary, because some browsers, like IE11, will call the focus handlers asynchronously, * while others won't fire them at all if the browser window is not focused. * * patchElementFocus(triggerEl); */ export function patchElementFocus(element) { // https://github.com/ngneat/spectator/issues/373 - Don't patch when using JSDOM, eg in Jest if (!isRunningInJsDom() && element[IS_FOCUS_PATCHED_PROP] === undefined) { const originalFocus = element.focus.bind(element); element.focus = (options) => { const focusEventWatcher = new FocusEventWatcher(element); // Sets document.activeElement. May or may not send focus + blur events originalFocus(options); focusEventWatcher.ensureFocusEvents(); }; element.blur = () => dispatchFakeEvent(element, 'blur'); element[IS_FOCUS_PATCHED_PROP] = true; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlbWVudC1mb2N1cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL3NwZWN0YXRvci9zcmMvbGliL2ludGVybmFscy9lbGVtZW50LWZvY3VzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUU1QyxtR0FBbUc7QUFDbkcsTUFBTSxxQkFBcUIsR0FBRyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUV2RCw0R0FBNEc7QUFDNUcsTUFBTSxpQkFBaUI7SUFRckIsWUFBNkIsT0FBb0I7UUFBcEIsWUFBTyxHQUFQLE9BQU8sQ0FBYTtRQUxqRCx5RUFBeUU7UUFDakUsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUN6QiwrREFBK0Q7UUFDdkQsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUd2QixJQUFJLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQztRQUM3QyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsT0FBTyxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUM7UUFDOUQsSUFBSSxDQUFDLGtCQUFrQixFQUFFLGdCQUFnQixDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBRU0sV0FBVyxDQUFDLEVBQUUsSUFBSSxFQUFTO1FBQ2hDLElBQUksSUFBSSxLQUFLLE9BQU8sRUFBRSxDQUFDO1lBQ3JCLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDO1FBQ3ZCLENBQUM7YUFBTSxJQUFJLElBQUksS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUMzQixJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQztRQUN2QixDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ksaUJBQWlCO1FBQ3RCLElBQUksQ0FBQyxPQUFPLENBQUMsbUJBQW1CLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ2hELElBQUksQ0FBQyxrQkFBa0IsRUFBRSxtQkFBbUIsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFFM0QsdUNBQXVDO1FBQ3ZDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1lBQzlDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUNyRCxDQUFDO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNuQixpQkFBaUIsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsOEJBQThCO1FBQzFFLENBQUM7SUFDSCxDQUFDO0NBQ0Y7QUFFRDs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsaUJBQWlCLENBQUMsT0FBb0I7SUFDcEQsNEZBQTRGO0lBQzVGLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxJQUFJLE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQ3hFLE1BQU0sYUFBYSxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2xELE9BQU8sQ0FBQyxLQUFLLEdBQUcsQ0FBQyxPQUFPLEVBQUUsRUFBRTtZQUMxQixNQUFNLGlCQUFpQixHQUFHLElBQUksaUJBQWlCLENBQUMsT0FBTyxDQUFDLENBQUM7WUFFekQsdUVBQXVFO1lBQ3ZFLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUV2QixpQkFBaUIsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQ3hDLENBQUMsQ0FBQztRQUNGLE9BQU8sQ0FBQyxJQUFJLEdBQUcsR0FBRyxFQUFFLENBQUMsaUJBQWlCLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3hELE9BQU8sQ0FBQyxxQkFBcUIsQ0FBQyxHQUFHLElBQUksQ0FBQztJQUN4QyxDQUFDO0FBQ0gsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGRpc3BhdGNoRmFrZUV2ZW50IH0gZnJvbSAnLi4vZGlzcGF0Y2gtZXZlbnRzJztcbmltcG9ydCB7IGlzUnVubmluZ0luSnNEb20gfSBmcm9tICcuLi91dGlscyc7XG5cbi8qKiBQcm9wZXJ0eSBhZGRlZCB0byBIVE1MIEVsZW1lbnRzIHRvIGVuc3VyZSB3ZSBkb24ndCBkb3VibGUtcGF0Y2ggZm9jdXMgbWV0aG9kcyBvbiBhbiBlbGVtZW50LiAqL1xuY29uc3QgSVNfRk9DVVNfUEFUQ0hFRF9QUk9QID0gU3ltYm9sKCdpc0ZvY3VzUGF0Y2hlZCcpO1xuXG4vKiogRW5zdXJlcyB0aGF0IGEgc2luZ2xlIHNldCBvZiBtYXRjaGluZyBmb2N1cyBhbmQgYmx1ciBldmVudHMgb2NjdXIgd2hlbiBIVE1MRWxlbWVudC5mb2N1cygpIGlzIGNhbGxlZC4gKi9cbmNsYXNzIEZvY3VzRXZlbnRXYXRjaGVyIGltcGxlbWVudHMgRXZlbnRMaXN0ZW5lck9iamVjdCB7XG4gIHByaXZhdGUgcmVhZG9ubHkgcHJpb3JBY3RpdmVFbGVtZW50OiBFbGVtZW50IHwgbnVsbDtcblxuICAvKiogU2V0IHRvIHRydWUgd2hlbiBicm93c2VyIHNlbmRzIGEgYmx1ciBldmVudCBmb3IgcHJpb3JBY3RpdmVFbGVtZW50ICovXG4gIHByaXZhdGUgX2JsdXJyZWQgPSBmYWxzZTtcbiAgLyoqIFNldCB0byB0cnVlIHdoZW4gYnJvd3NlciBzZW5kcyBhIGZvY3VzIGV2ZW50IGZvciBlbGVtZW50ICovXG4gIHByaXZhdGUgX2ZvY3VzZWQgPSBmYWxzZTtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IGVsZW1lbnQ6IEhUTUxFbGVtZW50KSB7XG4gICAgdGhpcy5lbGVtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2ZvY3VzJywgdGhpcyk7XG4gICAgdGhpcy5wcmlvckFjdGl2ZUVsZW1lbnQgPSBlbGVtZW50Lm93bmVyRG9jdW1lbnQuYWN0aXZlRWxlbWVudDtcbiAgICB0aGlzLnByaW9yQWN0aXZlRWxlbWVudD8uYWRkRXZlbnRMaXN0ZW5lcignYmx1cicsIHRoaXMpO1xuICB9XG5cbiAgcHVibGljIGhhbmRsZUV2ZW50KHsgdHlwZSB9OiBFdmVudCk6IHZvaWQge1xuICAgIGlmICh0eXBlID09PSAnZm9jdXMnKSB7XG4gICAgICB0aGlzLl9mb2N1c2VkID0gdHJ1ZTtcbiAgICB9IGVsc2UgaWYgKHR5cGUgPT09ICdibHVyJykge1xuICAgICAgdGhpcy5fYmx1cnJlZCA9IHRydWU7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIElmIGZvY3VzIGFuZCBibHVyIGV2ZW50cyBoYXZlbid0IG9jY3VycmVkLCBmaXJlIGZha2Ugb25lcy5cbiAgICovXG4gIHB1YmxpYyBlbnN1cmVGb2N1c0V2ZW50cygpIHtcbiAgICB0aGlzLmVsZW1lbnQucmVtb3ZlRXZlbnRMaXN0ZW5lcignZm9jdXMnLCB0aGlzKTtcbiAgICB0aGlzLnByaW9yQWN0aXZlRWxlbWVudD8ucmVtb3ZlRXZlbnRMaXN0ZW5lcignYmx1cicsIHRoaXMpO1xuXG4gICAgLy8gRW5zdXJlIHByaW9yQWN0aXZlRWxlbWVudCBpcyBibHVycmVkXG4gICAgaWYgKCF0aGlzLl9ibHVycmVkICYmIHRoaXMucHJpb3JBY3RpdmVFbGVtZW50KSB7XG4gICAgICBkaXNwYXRjaEZha2VFdmVudCh0aGlzLnByaW9yQWN0aXZlRWxlbWVudCwgJ2JsdXInKTtcbiAgICB9XG5cbiAgICBpZiAoIXRoaXMuX2ZvY3VzZWQpIHtcbiAgICAgIGRpc3BhdGNoRmFrZUV2ZW50KHRoaXMuZWxlbWVudCwgJ2ZvY3VzJyk7IC8vIE5lZWRlZCB0byBjYXVzZSBmb2N1cyBldmVudFxuICAgIH1cbiAgfVxufVxuXG4vKipcbiAqIFBhdGNoZXMgYW4gZWxlbWVudCdzIGZvY3VzIGFuZCBibHVyIG1ldGhvZHMgdG8gZW1pdCBldmVudHMgY29uc2lzdGVudGx5IGFuZCBwcmVkaWN0YWJseSBpbiB0ZXN0cy5cbiAqIFRoaXMgaXMgbmVjZXNzYXJ5LCBiZWNhdXNlIHNvbWUgYnJvd3NlcnMsIGxpa2UgSUUxMSwgd2lsbCBjYWxsIHRoZSBmb2N1cyBoYW5kbGVycyBhc3luY2hyb25vdXNseSxcbiAqIHdoaWxlIG90aGVycyB3b24ndCBmaXJlIHRoZW0gYXQgYWxsIGlmIHRoZSBicm93c2VyIHdpbmRvdyBpcyBub3QgZm9jdXNlZC5cbiAqXG4gKiBwYXRjaEVsZW1lbnRGb2N1cyh0cmlnZ2VyRWwpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcGF0Y2hFbGVtZW50Rm9jdXMoZWxlbWVudDogSFRNTEVsZW1lbnQpOiB2b2lkIHtcbiAgLy8gaHR0cHM6Ly9naXRodWIuY29tL25nbmVhdC9zcGVjdGF0b3IvaXNzdWVzLzM3MyAtIERvbid0IHBhdGNoIHdoZW4gdXNpbmcgSlNET00sIGVnIGluIEplc3RcbiAgaWYgKCFpc1J1bm5pbmdJbkpzRG9tKCkgJiYgZWxlbWVudFtJU19GT0NVU19QQVRDSEVEX1BST1BdID09PSB1bmRlZmluZWQpIHtcbiAgICBjb25zdCBvcmlnaW5hbEZvY3VzID0gZWxlbWVudC5mb2N1cy5iaW5kKGVsZW1lbnQpO1xuICAgIGVsZW1lbnQuZm9jdXMgPSAob3B0aW9ucykgPT4ge1xuICAgICAgY29uc3QgZm9jdXNFdmVudFdhdGNoZXIgPSBuZXcgRm9jdXNFdmVudFdhdGNoZXIoZWxlbWVudCk7XG5cbiAgICAgIC8vIFNldHMgZG9jdW1lbnQuYWN0aXZlRWxlbWVudC4gTWF5IG9yIG1heSBub3Qgc2VuZCBmb2N1cyArIGJsdXIgZXZlbnRzXG4gICAgICBvcmlnaW5hbEZvY3VzKG9wdGlvbnMpO1xuXG4gICAgICBmb2N1c0V2ZW50V2F0Y2hlci5lbnN1cmVGb2N1c0V2ZW50cygpO1xuICAgIH07XG4gICAgZWxlbWVudC5ibHVyID0gKCkgPT4gZGlzcGF0Y2hGYWtlRXZlbnQoZWxlbWVudCwgJ2JsdXInKTtcbiAgICBlbGVtZW50W0lTX0ZPQ1VTX1BBVENIRURfUFJPUF0gPSB0cnVlO1xuICB9XG59XG4iXX0=