diff --git a/src/signals/effect.test.ts b/src/signals/effect.test.ts index dea683f..cdfa54b 100644 --- a/src/signals/effect.test.ts +++ b/src/signals/effect.test.ts @@ -1,3 +1,4 @@ +import { Action, CancelAction, Scheduler } from 'hydroactive/signals.js'; import { effect } from './effect.js'; import { StabilityTracker } from './schedulers/stability-tracker.js'; import { TestScheduler } from './schedulers/test-scheduler.js'; @@ -139,6 +140,27 @@ describe('effect', () => { dispose(); }); + it('handles synchronous scheduling', () => { + const scheduler = new SyncScheduler(); + const value = signal(1); + const action = jasmine.createSpy<() => void>('action') + .and.callFake(() => value()); + + const dispose = effect(action, scheduler); + expect(action).toHaveBeenCalledOnceWith(); + action.calls.reset(); + + value.set(2); + expect(action).toHaveBeenCalledOnceWith(); + action.calls.reset(); + + // Regression test: Dropped second+ post-init schedule operations. + value.set(3); + expect(action).toHaveBeenCalledOnceWith(); + + dispose(); + }); + describe('dispose', () => { it('cleans up the effect', () => { const tracker = StabilityTracker.from(); @@ -197,3 +219,10 @@ describe('effect', () => { }); }); }); + +class SyncScheduler implements Scheduler { + public schedule(action: Action): CancelAction { + action(); + return () => {}; + } +} diff --git a/src/signals/effect.ts b/src/signals/effect.ts index 259381e..73fe282 100644 --- a/src/signals/effect.ts +++ b/src/signals/effect.ts @@ -30,22 +30,24 @@ export function effect( }); let cancelNextCall: CancelAction | undefined; + let scheduled = false; consumer.listen(() => { // If already scheduled, nothing to do. - if (cancelNextCall) return; + // It might look like we could drop `scheduled` and use the presence of + // `cancelNextCall` to know whether an event is scheduled, but this would + // not work for a synchronous `Scheduler`. + if (scheduled) return; + scheduled = true; cancelNextCall = scheduler.schedule(() => { - cancelNextCall = undefined; + scheduled = false; consumer.record(callback); }); }); return () => { cancelInitialCall(); - if (cancelNextCall) { - cancelNextCall(); - cancelNextCall = undefined; - } + if (scheduled) cancelNextCall!(); consumer.destroy(); }; }