Skip to content

Commit

Permalink
fix(esl-utils): SynteticEventTarget no longer mutate event.target
Browse files Browse the repository at this point in the history
… without need; `event.currentTarget` fix to current instance

Closes: #1534 (ESLDecoratedEventTarget` mutate original event's target)
  • Loading branch information
ala-n committed Mar 13, 2023
1 parent 8252988 commit d5e2c78
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class ESLEventTargetDecorator<Args extends any[]> extends SyntheticEventT

/** @returns decorated handler */
protected createHandler(): EventListener {
return this.decorator((event: Event) => this.dispatchEvent(event, this.target), ...this.params);
return this.decorator(this.dispatchEvent.bind(this), ...this.params);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {SyntheticEventTarget} from '../../../esl-utils/dom/events/target';
import {overrideEvent} from '../../../esl-utils/dom/events/misc';

/** Adapter class for {@link ResizeObserver} that implements {@link EventTarget} */
export class ESLResizeObserverTarget extends SyntheticEventTarget {
Expand All @@ -17,7 +18,8 @@ export class ESLResizeObserverTarget extends SyntheticEventTarget {
const adapter = this.mapping.get(detail.target);
if (!adapter) return;
const event = new CustomEvent('resize', {detail});
adapter.dispatchEvent(event, adapter.target);
overrideEvent(event, 'target', adapter.target);
adapter.dispatchEvent(event);
}

/** Creates {@link ESLResizeObserverTarget} instance for the {@link Element} */
Expand Down
19 changes: 19 additions & 0 deletions src/modules/esl-event-listener/test/targets/decorator.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {identity} from '../../../esl-utils/misc/functions';
import {debounce} from '../../../esl-utils/async/debounce';
import {throttle} from '../../../esl-utils/async/throttle';
import {ESLEventUtils} from '../../core/api';
Expand All @@ -24,6 +25,24 @@ describe('ESLEventUtils.decorate proxy', () => {
});
});

describe('ESLEventUtils.decorate event target', () => {
const et = document.createElement('div');
const dec = ESLEventUtils.decorate(et, identity);
const handler = jest.fn();

beforeEach(() => dec.addEventListener('click', handler));
afterEach(() => dec.removeEventListener('click', handler));

test('ESLEventUtils.decorate does not replace event.target', () => {
et.dispatchEvent(new Event('click'));
expect(handler).lastCalledWith(expect.objectContaining({target: et}));
});
test('ESLEventUtils.decorate does not replace event.target', () => {
et.dispatchEvent(new Event('click'));
expect(handler).lastCalledWith(expect.objectContaining({currentTarget: dec}));
});
});

describe('ESLEventUtils.decorate attribute processing', () => {
const el = document.createElement('div');
const handler = jest.fn();
Expand Down
21 changes: 21 additions & 0 deletions src/modules/esl-utils/dom/events/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,24 @@ export const dispatchCustomEvent = (el: EventTarget, eventName: string, eventIni
}, eventInit || {});
return el.dispatchEvent(new CustomEvent(eventName, init));
};

/**
* Overrides {@link Event} `target` property
* @param event - {@link Event} to override
* @param key - {@link Event} property
* @param target - {@link EventTarget} to setup
* @returns original event
*/
export const overrideEvent = (
event: Event,
key: keyof Event,
target: null | EventTarget | (() => null | EventTarget)
): Event => {
const provider = typeof target === 'function' ? target : ((): null | EventTarget => target);
Object.defineProperty(event, key, {
get: provider,
enumerable: true,
configurable: true
});
return event;
};
20 changes: 10 additions & 10 deletions src/modules/esl-utils/dom/events/target.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {overrideEvent} from './misc';

/**
* Synthetic implementation of EventTarget
* Replicates behavior of native event
Expand Down Expand Up @@ -38,16 +40,14 @@ export class SyntheticEventTarget implements EventTarget {
this._listeners[type] = this._listeners[type].filter((cb) => cb !== callback);
}

public dispatchEvent(e: Event, target: EventTarget = this): boolean {
const targetDescriptor: PropertyDescriptor = {
get: () => target,
enumerable: true,
configurable: true
};
Object.defineProperty(e, 'target', targetDescriptor);
Object.defineProperty(e, 'currentTarget', targetDescriptor);
Object.defineProperty(e, 'srcElement', targetDescriptor);

public dispatchEvent(e: Event): boolean;
/** @deprecated use `overrideEvent` to declare `target` */
public dispatchEvent(e: Event, target?: EventTarget): boolean;
public dispatchEvent(e: Event, target?: EventTarget): boolean {
overrideEvent(e, 'currentTarget', this);
if (target) overrideEvent(e, 'target', target); // TODO: remove in 5.0.0
if (!e.target) overrideEvent(e, 'target', this);
if (!e.srcElement) overrideEvent(e, 'srcElement', e.target); // TODO: remove in 5.0.0
this._listeners[e.type]?.forEach((listener) => {
if (typeof listener === 'function') listener.call(this, e);
else listener.handleEvent.call(listener, e);
Expand Down

0 comments on commit d5e2c78

Please sign in to comment.