Skip to content

Commit

Permalink
Removes ComponentRef.prototype.listen.
Browse files Browse the repository at this point in the history
This has been obsoleted by `ElementAccessor.prototype.listen`.
  • Loading branch information
dgp1130 committed Sep 4, 2024
1 parent 9df7463 commit 193f4a6
Show file tree
Hide file tree
Showing 2 changed files with 3 additions and 253 deletions.
191 changes: 3 additions & 188 deletions src/component-ref.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import './testing/noop-component.js';

import { ComponentRef, type OnDisconnect, type OnConnect } from './component-ref.js';
import { ElementRef } from './element-ref.js';
import { type AttrSerializable, type AttrSerializer, type ElementSerializable, type ElementSerializer, toSerializer } from './serializers.js';
import { type WriteableSignal, signal } from './signals.js';
import { parseHtml } from './testing/html-parser.js';
import { NoopComponent } from './testing/noop-component.js';
import { signal } from './signals.js';

describe('component-ref', () => {
afterEach(() => {
Expand Down Expand Up @@ -275,189 +274,5 @@ describe('component-ref', () => {
expect(effect).toHaveBeenCalledOnceWith();
});
});

describe('listen', () => {
it('listens invokes the given callback when the specified event is triggered', () => {
const el = document.createElement('noop-component');
document.body.appendChild(el);
const ref = el.getComponentRef();

const handler = jasmine.createSpy<(evt: Event) => void>('handler');

ref.listen(ref.host, 'click', handler);
expect(handler).not.toHaveBeenCalled();

ref.host.native.click();

expect(handler).toHaveBeenCalledOnceWith(jasmine.any(Event));
});

it('removes event listener on disconnect', () => {
const el = document.createElement('noop-component');
const ref = el.getComponentRef();

const addSpy = spyOn(el, 'addEventListener').and.callThrough();
const removeSpy = spyOn(el, 'removeEventListener').and.callThrough();

const handler = jasmine.createSpy<() => void>('handler');

// Start listening while disconnected, nothing should happen yet.
ref.listen(ref.host, 'click', handler);
expect(addSpy).not.toHaveBeenCalled();
expect(removeSpy).not.toHaveBeenCalled();

ref.host.native.click();
expect(handler).not.toHaveBeenCalled();

// On connection, start listening.
document.body.appendChild(el);
expect(addSpy).toHaveBeenCalledTimes(1);
expect(removeSpy).not.toHaveBeenCalled();
expect(handler).not.toHaveBeenCalled();
addSpy.calls.reset();

// React to events.
ref.host.native.click();
expect(handler).toHaveBeenCalledTimes(1);
handler.calls.reset();

// On disconnect, stop listening.
el.remove();
expect(addSpy).not.toHaveBeenCalled();
expect(removeSpy).toHaveBeenCalledTimes(1);
expect(handler).not.toHaveBeenCalled();
removeSpy.calls.reset();

// Stop reacting to events.
ref.host.native.click();
expect(handler).not.toHaveBeenCalled();
});

it('listens for events for the provided `ElementRef`', () => {
const el = parseHtml(NoopComponent, `
<noop-component>
<div id="first">
<div id="child"></div>
</div>
<div id="second"></div>
</noop-component>
`);
document.body.appendChild(el);
const ref = el.getComponentRef();

const handler = jasmine.createSpy<() => void>('handler');

ref.listen(ref.host.query('div#first'), 'click', handler);

// Listen for direct events.
ref.host.query('div#first').native.click();
expect(handler).toHaveBeenCalledTimes(1);
handler.calls.reset();

// Listen for descendant events.
ref.host.query('div#child').native.click();
expect(handler).toHaveBeenCalledTimes(1);
handler.calls.reset();

// Do not listen for sibling events.
ref.host.query('div#second').native.click();
expect(handler).not.toHaveBeenCalled();

// Do not listen for ancestor events.
ref.host.native.click();
expect(handler).not.toHaveBeenCalled();
});

it('listens for events for the provided selector', () => {
const el = parseHtml(NoopComponent, `
<noop-component>
<div id="first">
<div id="child"></div>
</div>
<div id="second"></div>
</noop-component>
`);
document.body.appendChild(el);
const ref = el.getComponentRef();

const handler = jasmine.createSpy<() => void>('handler');

ref.listen('div#first', 'click', handler);

// Listen for direct events.
ref.host.query('div#first').native.click();
expect(handler).toHaveBeenCalledTimes(1);
handler.calls.reset();

// Listen for descendant events.
ref.host.query('div#child').native.click();
expect(handler).toHaveBeenCalledTimes(1);
handler.calls.reset();

// Do not listen for sibling events.
ref.host.query('div#second').native.click();
expect(handler).not.toHaveBeenCalled();

// Do not listen for ancestor events.
ref.host.native.click();
expect(handler).not.toHaveBeenCalled();
});

it('throws an error if given a selector which does not match anything', () => {
const el = document.createElement('noop-component');
document.body.appendChild(el);
const ref = el.getComponentRef();

expect(() => ref.listen('#does-not-exist', 'click', () => {}))
.toThrowError();
});

it('supports custom events', () => {
const el = document.createElement('noop-component');
document.body.appendChild(el);
const ref = el.getComponentRef();

const handler = jasmine.createSpy<(evt: Event) => void>('handler');

ref.listen(ref.host, 'custom-event', handler);

const evt = new CustomEvent('custom-event');
ref.host.native.dispatchEvent(evt);

expect(handler).toHaveBeenCalledOnceWith(evt);
});

it('propagates the `capture` option', () => {
const el = document.createElement('noop-component');
document.body.appendChild(el);
const ref = el.getComponentRef();

spyOn(el, 'addEventListener').and.callThrough();

ref.listen(ref.host, 'click', () => {}, { capture: true });

expect(el.addEventListener).toHaveBeenCalledOnceWith(
'click',
jasmine.any(Function),
jasmine.objectContaining({ capture: true }),
);
});

it('propagates the `passive` option', () => {
const el = document.createElement('noop-component');
document.body.appendChild(el);
const ref = el.getComponentRef();

spyOn(el, 'addEventListener').and.callThrough();

ref.listen(ref.host, 'click', () => {}, { passive: true });

expect(el.addEventListener).toHaveBeenCalledOnceWith(
'click',
jasmine.any(Function),
jasmine.objectContaining({ passive: true }),
);
});
});
});
});
65 changes: 0 additions & 65 deletions src/component-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,58 +120,6 @@ export class ComponentRef {
});
}

/**
* Creates an event listener for the given event which invokes the provided
* handler callback function. This listener is automatically created and
* removed as the component is connected and disconnected from the DOM,
* meaning the listener does not leak memory and does not need to be manually
* cleaned up.
*
* @param elementOrSelector An {@link ElementRef} or selector string to query
* inside the component. This element holds the created event listener,
* meaning only events on this element or its descendants will trigger the
* listener.
* @param event The name of the event to listen for.
* @param handler The event handler to invoke whenever an associated event is
* dispatched.
* @param capture [See `capture` documentation for `addEventListener`.](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#capture)
* @param passive [See `passive` documentation for `addEventListener`.](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive)
*/
// Type `HTMLElement` events for improved autocompletion.
public listen<EventName extends keyof AllElementsEventMap>(
elementOrSelector: ElementRef<Element> | string,
event: EventName,
handler: (event: AllElementsEventMap[EventName]) => void,
options?: { capture?: boolean, passive?: boolean },
): void;

// Overload with generic `string` types so we don't disallow custom events.
public listen(
elementOrSelector: ElementRef<Element> | string,
event: string,
handler: (event: Event) => void,
options?: { capture?: boolean, passive?: boolean },
): void;

public listen(
elementOrSelector: ElementRef<Element> | string,
event: string,
handler: (event: Event) => void,
{ capture, passive }: { capture?: boolean, passive?: boolean } = {},
): void {
const element = elementOrSelector instanceof ElementRef
? elementOrSelector
: this.host.query(elementOrSelector);

this.connected(() => {
element.native.addEventListener(event, handler, { capture, passive });

return () => {
element.native.removeEventListener(event, handler, { capture })
};
});
}

/**
* Invokes the given {@link OnConnect} handler and registers its disconnect
* callback if provided.
Expand All @@ -181,16 +129,3 @@ export class ComponentRef {
if (onDisconnect) this.#disconnectedCallbacks.push(onDisconnect);
}
}

// An attempt to capture all the event maps a user might reasonably encounter
// in an element discovered by an element query inside a component. Almost
// certainly not exhaustive.
type AllElementsEventMap =
& HTMLElementEventMap
& SVGElementEventMap
& SVGSVGElementEventMap
& MathMLElementEventMap
& HTMLVideoElementEventMap
& HTMLMediaElementEventMap
& HTMLFrameSetElementEventMap
;

0 comments on commit 193f4a6

Please sign in to comment.