import { describe, expect, it, vi } from 'vitest' import { EventEmitter } from '../../src/utils/event-emitter.js' type Events = { tick: number; done: void } describe('EventEmitter', () => { it('delivers payloads as-is (no cloning or freezing) and supports off', () => { const emitter = new EventEmitter() const seen: unknown[] = [] const payload = { n: 1 } const off = emitter.on('obj', (p) => seen.push(p)) expect(emitter.emit('obj', payload)).toBe(true) off() expect(emitter.emit('obj', payload)).toBe(false) expect(seen[0]).toBe(payload) expect(Object.isFrozen(payload)).toBe(false) }) it('once fires a single time; listener errors are logged, not thrown', () => { const error = vi.spyOn(console, 'error').mockImplementation(() => {}) const emitter = new EventEmitter() const once = vi.fn() emitter.once('tick', once) emitter.on('tick', () => { throw new Error('boom') }) emitter.emit('tick', 1) emitter.emit('tick', 2) expect(once).toHaveBeenCalledTimes(1) expect(error).toHaveBeenCalledTimes(2) error.mockRestore() }) it('debounces and cancels pending debounced calls on off', async () => { vi.useFakeTimers() const emitter = new EventEmitter() const listener = vi.fn() const off = emitter.on('tick', listener, 50) emitter.emit('tick', 1) emitter.emit('tick', 2) vi.advanceTimersByTime(60) expect(listener).toHaveBeenCalledExactlyOnceWith(2) emitter.emit('tick', 3) off() vi.advanceTimersByTime(60) expect(listener).toHaveBeenCalledTimes(1) vi.useRealTimers() }) it('waitFor resolves on a matching payload and rejects on timeout', async () => { const emitter = new EventEmitter() const waiting = emitter.waitFor('tick', (n) => n > 1) emitter.emit('tick', 1) emitter.emit('tick', 2) expect(await waiting).toBe(2) await expect(emitter.waitFor('done', () => true, 5)).rejects.toThrow(/Timed out waiting for "done"/) }) })