Files
PotentialStorage/test/utils/event-emitter.test.ts
T
Claude 4ffbc7e78d
CI / Node 24 (pull_request) Failing after 12s
CI / Node 22 (pull_request) Successful in 14s
CI / Node 26 (pull_request) Failing after 21s
Add KVStorageComposite, IndexedDB storage and SSE change streams
- Every KV storage emits change events (base class owns writeMany/removeMany/
  writeIfAbsent; implementations provide protected apply* methods)
- KVStorageHttpServer streams changes over SSE (GET /events) with a resumable
  history; KVStorageHttp.watch() subscribes and resumes via Last-Event-ID
- KVStorageIndexedDB for local persistence
- KVStorageComposite keeps replicas of a source in sync: local reads, resume
  instead of full sync, offline outbox replayed in order, echo tracking
- Values may contain bigint/Uint8Array (Extended JSON over the network)
- Vendor sse-session, event-emitter and extended-json (no runtime deps)
- Freeze child nodes so shadowed state on children fails loudly
- CI on Node 22, 24 and 26

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qxbkp4tA7G5SCWjzWuxmE
2026-10-23 08:41:42 +00:00

59 lines
2.0 KiB
TypeScript

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<Events & { obj: { n: number } }>()
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<Events>()
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<Events>()
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<Events>()
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"/)
})
})