import { pollLoop, PollLoopTimeoutError, type PollTickResult } from './pollLoop' describe('pollLoop', () => { it('returns immediately when tick returns stop', async () => { const tick = jest.fn().mockResolvedValue({ kind: 'stop', value: 42, } as PollTickResult) await expect( pollLoop({ tick, intervalMs: 1000, timeoutMs: 5000, }), ).resolves.toBe(42) expect(tick).toHaveBeenCalledTimes(1) }) it('rejects when tick returns throw', async () => { const err = new Error('tick failed') const tick = jest.fn().mockResolvedValue({ kind: 'throw', error: err, } as PollTickResult) await expect( pollLoop({ tick, intervalMs: 100, timeoutMs: 1000, }), ).rejects.toThrow('tick failed') }) it('throws PollLoopTimeoutError when tick always continues', async () => { jest.useFakeTimers() const tick = jest.fn().mockResolvedValue({ kind: 'continue' } as const) const p = pollLoop({ tick, intervalMs: 10, timeoutMs: 100, }) const expectTimeout = expect(p).rejects.toBeInstanceOf(PollLoopTimeoutError) await jest.runAllTimersAsync() await expectTimeout jest.useRealTimers() }) it('waits initialDelayMs before first tick', async () => { jest.useFakeTimers() const tick = jest.fn().mockResolvedValue({ kind: 'stop', value: true, } as PollTickResult) const p = pollLoop({ tick, intervalMs: 100, initialDelayMs: 300, timeoutMs: 2000, }) expect(tick).not.toHaveBeenCalled() await jest.advanceTimersByTimeAsync(299) expect(tick).not.toHaveBeenCalled() await jest.advanceTimersByTimeAsync(1) await expect(p).resolves.toBe(true) expect(tick).toHaveBeenCalledTimes(1) jest.useRealTimers() }) it('increases sleep interval with backoff until maxIntervalMs', async () => { jest.useFakeTimers() let n = 0 const tick = jest.fn().mockImplementation(async () => { n += 1 if (n < 3) { return { kind: 'continue' } as const } return { kind: 'stop', value: 'ok' } as PollTickResult }) const p = pollLoop({ tick, intervalMs: 10, timeoutMs: 10_000, backoff: { factor: 100, maxIntervalMs: 500 }, }) const settled = p.then((v) => v) await Promise.resolve() expect(tick).toHaveBeenCalledTimes(1) await jest.advanceTimersByTimeAsync(10) await Promise.resolve() expect(tick).toHaveBeenCalledTimes(2) await jest.advanceTimersByTimeAsync(500) await Promise.resolve() expect(tick).toHaveBeenCalledTimes(3) await settled expect(await settled).toBe('ok') jest.useRealTimers() }) })