diff --git a/src/component-ref.test.ts b/src/component-ref.test.ts index 4126fd5..e9fcbb4 100644 --- a/src/component-ref.test.ts +++ b/src/component-ref.test.ts @@ -24,147 +24,6 @@ describe('component-ref', () => { }); }); - describe('connected', () => { - it('invokes the given callback on connect', () => { - const onConnect = jasmine.createSpy('onConnect'); - - const el = document.createElement('noop-component'); - const ref = el.getComponentRef(); - - ref.connected(onConnect); - expect(onConnect).not.toHaveBeenCalled(); - - document.body.appendChild(el); - expect(onConnect).toHaveBeenCalledOnceWith(); - }); - - it('invokes the given callback on repeated connections', () => { - const onConnect = jasmine.createSpy('onConnect'); - - const el = document.createElement('noop-component'); - const ref = el.getComponentRef(); - - ref.connected(onConnect); - expect(onConnect).not.toHaveBeenCalled(); - - // Called on first connection. - document.body.appendChild(el); - expect(onConnect).toHaveBeenCalledOnceWith(); - - onConnect.calls.reset(); - - el.remove(); - expect(onConnect).not.toHaveBeenCalled(); - - // Called again on second connection. - document.body.appendChild(el); - expect(onConnect).toHaveBeenCalledOnceWith(); - }); - - it('invokes the connected disposer on disconnect', () => { - const onDisconnect = jasmine.createSpy('onDisconnect'); - - const el = document.createElement('noop-component'); - const ref = el.getComponentRef(); - - ref.connected(() => onDisconnect); - expect(onDisconnect).not.toHaveBeenCalled(); - - document.body.appendChild(el); - expect(onDisconnect).not.toHaveBeenCalled(); - - el.remove(); - expect(onDisconnect).toHaveBeenCalledOnceWith(); - }); - - it('refreshes the disconnect listener on each connection', () => { - const onDisconnect1 = jasmine.createSpy('onDisconnect1'); - const onDisconnect2 = jasmine.createSpy('onDisconnect2'); - - const el = document.createElement('noop-component'); - const ref = el.getComponentRef(); - - ref.connected(jasmine.createSpy().and.returnValues( - onDisconnect1, - onDisconnect2, - )); - expect(onDisconnect1).not.toHaveBeenCalled(); - expect(onDisconnect2).not.toHaveBeenCalled(); - - // First connect. - document.body.appendChild(el); - expect(onDisconnect1).not.toHaveBeenCalled(); - expect(onDisconnect2).not.toHaveBeenCalled(); - - // First disconnect should only call the first disconnect listener. - el.remove(); - expect(onDisconnect1).toHaveBeenCalledOnceWith(); - expect(onDisconnect2).not.toHaveBeenCalled(); - - onDisconnect1.calls.reset(); - - // Second connect. - document.body.appendChild(el); - expect(onDisconnect1).not.toHaveBeenCalled(); - expect(onDisconnect2).not.toHaveBeenCalled(); - - // Second disconnect should only call the second disconnect listener. - el.remove(); - expect(onDisconnect1).not.toHaveBeenCalled(); - expect(onDisconnect2).toHaveBeenCalledOnceWith(); - }); - - it('manages multiple connect callbacks', () => { - const onConnect1 = jasmine.createSpy('onConnect1'); - const onConnect2 = jasmine.createSpy('onConnect2'); - - const el = document.createElement('noop-component'); - const ref = el.getComponentRef(); - - ref.connected(onConnect1); - ref.connected(onConnect2); - - expect(onConnect1).not.toHaveBeenCalled(); - expect(onConnect2).not.toHaveBeenCalled(); - - document.body.appendChild(el); - expect(onConnect1).toHaveBeenCalledOnceWith(); - expect(onConnect2).toHaveBeenCalledOnceWith(); - }); - - it('invokes the connect callback immediately when already connected', () => { - const onConnect = jasmine.createSpy('onConnect'); - - const el = document.createElement('noop-component'); - const ref = el.getComponentRef(); - - document.body.appendChild(el); - expect(onConnect).not.toHaveBeenCalled(); - - ref.connected(onConnect); - expect(onConnect).toHaveBeenCalledOnceWith(); - }); - - it('does not invoke the connect callback when disconnected', () => { - const onConnect = jasmine.createSpy('onConnect'); - - const el = document.createElement('noop-component'); - const ref = el.getComponentRef(); - - // Connect and disconnect the element. - document.body.appendChild(el); - el.remove(); - - // Should not be called because the element is currently disconnected. - ref.connected(onConnect); - expect(onConnect).not.toHaveBeenCalled(); - - // Should be called when connected. - document.body.appendChild(el); - expect(onConnect).toHaveBeenCalledOnceWith(); - }); - }); - describe('effect', () => { it('schedules the effect for the next animation frame', async () => { const el = document.createElement('noop-component'); diff --git a/src/component-ref.ts b/src/component-ref.ts index 76c198d..f63cd50 100644 --- a/src/component-ref.ts +++ b/src/component-ref.ts @@ -1,4 +1,4 @@ -import { Connectable, OnConnect } from './connectable.js'; +import { Connectable } from './connectable.js'; import { effect } from './signals.js'; import { UiScheduler } from './signals/schedulers/ui-scheduler.js'; @@ -33,37 +33,6 @@ export class ComponentRef { return await this.#scheduler.stable(); } - /** - * Sets up the given handler to be invoked whenever the component is connected - * to the DOM. If the handler returns a function, that function will be - * invoked the next time the component is disconnected. This provides a useful - * API for maintaining state which needs to be cleaned up while avoiding - * memory leaks in the component. - * - * The connect handler may be invoked multiple times if the component is - * disconnected and reconnected to the DOM. - * - * Example: - * - * ```typescript - * component('my-component', (comp) => { - * comp.connected(() => { - * console.log('I am connected!'); - * - * // Optional cleanup work to be run on disconnect. - * return () => { - * console.log('I am disconnected!'); - * }; - * }); - * }); - * ``` - * - * @param onConnect The function to invoke when the component is connected. - */ - public connected(onConnect: OnConnect): void { - this.#connectable.connected(onConnect); - } - /** * Schedules the side-effectful callback to be invoked and tracks signal usage * within it. When any dependency signal changes, the effect is re-run on the @@ -75,7 +44,7 @@ export class ComponentRef { * @param callback The side-effectful callback to be invoked. */ public effect(callback: () => void): void { - this.connected(() => { + this.#connectable.connected(() => { return effect(callback, this.#scheduler); }); } diff --git a/src/element-accessor.test.ts b/src/element-accessor.test.ts index 8ca0bae..247ec3b 100644 --- a/src/element-accessor.test.ts +++ b/src/element-accessor.test.ts @@ -289,11 +289,11 @@ describe('element-accessor', () => { it('listens invokes the given callback when the specified event is triggered', () => { const el = document.createElement('noop-component'); document.body.appendChild(el); - const comp = el.getComponentRef(); + const host = el.getComponentAccessor(); const handler = jasmine.createSpy<(evt: Event) => void>('handler'); - ElementAccessor.from(el).listen(comp, 'click', handler); + ElementAccessor.from(el).listen(host, 'click', handler); expect(handler).not.toHaveBeenCalled(); el.click(); @@ -303,7 +303,7 @@ describe('element-accessor', () => { it('removes event listener on disconnect', () => { const el = document.createElement('noop-component'); - const comp = el.getComponentRef(); + const host = el.getComponentAccessor(); const addSpy = spyOn(el, 'addEventListener').and.callThrough(); const removeSpy = spyOn(el, 'removeEventListener').and.callThrough(); @@ -311,7 +311,7 @@ describe('element-accessor', () => { const handler = jasmine.createSpy<() => void>('handler'); // Start listening while disconnected, nothing should happen yet. - ElementAccessor.from(el).listen(comp, 'click', handler); + ElementAccessor.from(el).listen(host, 'click', handler); expect(addSpy).not.toHaveBeenCalled(); expect(removeSpy).not.toHaveBeenCalled(); @@ -352,12 +352,11 @@ describe('element-accessor', () => { `); document.body.appendChild(el); - const comp = el.getComponentRef(); - const host = ElementAccessor.from(el); + const host = el.getComponentAccessor(); const handler = jasmine.createSpy<() => void>('handler'); - host.query('div#first').access().listen(comp, 'click', handler); + host.query('div#first').access().listen(host, 'click', handler); // Listen for direct events. host.query('div#first').access().element.click(); @@ -381,11 +380,11 @@ describe('element-accessor', () => { it('supports custom events', () => { const el = document.createElement('noop-component'); document.body.appendChild(el); - const comp = el.getComponentRef(); + const host = el.getComponentAccessor(); const handler = jasmine.createSpy<(evt: Event) => void>('handler'); - ElementAccessor.from(el).listen(comp, 'custom-event', handler); + ElementAccessor.from(el).listen(host, 'custom-event', handler); const evt = new CustomEvent('custom-event'); el.dispatchEvent(evt); @@ -396,11 +395,11 @@ describe('element-accessor', () => { it('propagates the `capture` option', () => { const el = document.createElement('noop-component'); document.body.appendChild(el); - const comp = el.getComponentRef(); + const host = el.getComponentAccessor(); spyOn(el, 'addEventListener').and.callThrough(); - ElementAccessor.from(el).listen(comp, 'click', () => {}, { + ElementAccessor.from(el).listen(host, 'click', () => {}, { capture: true, }); @@ -414,11 +413,11 @@ describe('element-accessor', () => { it('propagates the `passive` option', () => { const el = document.createElement('noop-component'); document.body.appendChild(el); - const comp = el.getComponentRef(); + const host = el.getComponentAccessor(); spyOn(el, 'addEventListener').and.callThrough(); - ElementAccessor.from(el).listen(comp, 'click', () => {}, { + ElementAccessor.from(el).listen(host, 'click', () => {}, { passive: true, }); diff --git a/src/hydroactive-component.test.ts b/src/hydroactive-component.test.ts index 1a1b386..85a4cae 100644 --- a/src/hydroactive-component.test.ts +++ b/src/hydroactive-component.test.ts @@ -187,113 +187,6 @@ describe('hydroactive-component', () => { }); }); - describe('_registerComponentRef', () => { - it('registers the `ComponentRef` and invokes its lifecycle hooks', () => { - const onDisconnect = jasmine.createSpy('onDisconnect'); - const onConnect = jasmine.createSpy('onConnect') - .and.returnValue(onDisconnect); - - customElements.define( - 'lifecycle-comp', - class extends HydroActiveComponent { - hydrate(): void {} - }, - ); - - const el = - document.createElement('lifecycle-comp') as HydroActiveComponent; - const accessor = ComponentAccessor.fromComponent(el); - const ref = ComponentRef._from(accessor); - el._registerComponentRef(ref); - - ref.connected(onConnect); - - // Invokes the `onConnect` listener when connected. - document.body.appendChild(el); - expect(onConnect).toHaveBeenCalledTimes(1); - expect(onDisconnect).not.toHaveBeenCalled(); - - onConnect.calls.reset(); - - // Invokes the `onDisconnect` listener when disconnected. - el.remove(); - expect(onConnect).not.toHaveBeenCalled(); - expect(onDisconnect).toHaveBeenCalledTimes(1); - }); - - it('does *not* invoke connect listener prior to hydration', () => { - let hydrated = false; - const onConnect = jasmine.createSpy('onConnect') - .and.callFake(() => { - // Connection happens before hydration. - expect(hydrated).toBeFalse(); - }); - - customElements.define( - 'hydration-connect', - class extends HydroActiveComponent { - override hydrate(): void { - hydrated = true; - } - }, - ); - - const el = document.createElement('hydration-connect') as - HydroActiveComponent; - const accessor = ComponentAccessor.fromComponent(el); - const ref = ComponentRef._from(accessor); - el._registerComponentRef(ref); - - ref.connected(onConnect); - expect(onConnect).not.toHaveBeenCalled(); - - document.body.appendChild(el); - - // Verify that `onConnect` was invoked, which itself should have - // verified that `hydrate` was *not* called before it. - expect(onConnect).toHaveBeenCalled(); - }); - }); - - describe('_connectable', () => { - it('triggers connected callback when the component is connected to the document', () => { - const onConnect = jasmine.createSpy('onConnect'); - - const el = document.createElement('noop-component'); - - el._connectable.connected(onConnect); - expect(onConnect).not.toHaveBeenCalled(); - - document.body.append(el); - expect(onConnect).toHaveBeenCalledOnceWith(); - }); - - it('triggers disconnected callback when the component is disconnected from the document', () => { - const onDisconnect = jasmine.createSpy('onDisconnect'); - - const el = document.createElement('noop-component'); - - el._connectable.connected(() => onDisconnect); - expect(onDisconnect).not.toHaveBeenCalled(); - - document.body.append(el); - expect(onDisconnect).not.toHaveBeenCalled(); - - el.remove(); - expect(onDisconnect).toHaveBeenCalledOnceWith(); - }); - - it('triggers connected callback immediately when the component is already connected to the document', () => { - const onConnect = jasmine.createSpy('onConnect'); - - const el = document.createElement('noop-component'); - document.body.append(el); - - el._connectable.connected(onConnect); - expect(onConnect).toHaveBeenCalledOnceWith(); - }); - }); - describe('stable', () => { it('resolves when the component is stable of all effects', async () => { const el = document.createElement('noop-component');