import { EventEmitter } from 'node:events'; import { describe, it, expect } from 'bun:test'; // ─── BrowserManager basic unit tests ───────────────────────────── describe('BrowserManager defaults', () => { it('getConnectionMode defaults to launched', async () => { const { BrowserManager } = await import('../src/browser-manager'); const bm = new BrowserManager(); expect(bm.getConnectionMode()).toBe('launched'); }); it('getRefMap returns empty array initially', async () => { const { BrowserManager } = await import('../src/browser-manager'); const bm = new BrowserManager(); expect(bm.getRefMap()).toEqual([]); }); }); // ─── resolveDisconnectCause ────────────────────────────────────── // // Pinning the clean-vs-crash distinction matters because gbd's // HealthMonitor consumes our exit code (0 = don't restart, !=0 = // restart). A regression here brings back the "Cmd+Q makes the browser // keep coming back" UX bug. function makeFakeBrowser(opts: { exitCode: number | null; signalCode: NodeJS.Signals | null; /** ms before emitting 'exit'; default = already exited at construction */ exitDelay?: number; }): { process(): { exitCode: number | null; signalCode: NodeJS.Signals | null; once: EventEmitter['once'] } } { const ee = new EventEmitter(); const state = { exitCode: opts.exitDelay != null ? null : opts.exitCode, signalCode: opts.exitDelay != null ? null : opts.signalCode, once: ee.once.bind(ee), }; if (opts.exitDelay != null) { setTimeout(() => { state.exitCode = opts.exitCode; state.signalCode = opts.signalCode; ee.emit('exit', opts.exitCode, opts.signalCode); }, opts.exitDelay); } return { process: () => state }; } describe('resolveDisconnectCause', () => { it('clean: process already exited with code 0', async () => { const { resolveDisconnectCause } = await import('../src/browser-manager'); const fake = makeFakeBrowser({ exitCode: 0, signalCode: null }); expect(await resolveDisconnectCause(fake as never)).toBe('clean'); }); it('crash: non-zero exit code', async () => { const { resolveDisconnectCause } = await import('../src/browser-manager'); const fake = makeFakeBrowser({ exitCode: 1, signalCode: null }); expect(await resolveDisconnectCause(fake as never)).toBe('crash'); }); it('crash: SIGSEGV', async () => { const { resolveDisconnectCause } = await import('../src/browser-manager'); const fake = makeFakeBrowser({ exitCode: null, signalCode: 'SIGSEGV' }); expect(await resolveDisconnectCause(fake as never)).toBe('crash'); }); it('crash: SIGKILL', async () => { const { resolveDisconnectCause } = await import('../src/browser-manager'); const fake = makeFakeBrowser({ exitCode: null, signalCode: 'SIGKILL' }); expect(await resolveDisconnectCause(fake as never)).toBe('crash'); }); it('clean: process exits asynchronously with code 0 within timeout', async () => { const { resolveDisconnectCause } = await import('../src/browser-manager'); const fake = makeFakeBrowser({ exitCode: 0, signalCode: null, exitDelay: 50 }); expect(await resolveDisconnectCause(fake as never)).toBe('clean'); }); it('crash: process exits asynchronously with non-zero code', async () => { const { resolveDisconnectCause } = await import('../src/browser-manager'); const fake = makeFakeBrowser({ exitCode: 137, signalCode: null, exitDelay: 50 }); expect(await resolveDisconnectCause(fake as never)).toBe('crash'); }); it('crash: null browser returns crash (defensive default)', async () => { const { resolveDisconnectCause } = await import('../src/browser-manager'); expect(await resolveDisconnectCause(null)).toBe('crash'); }); });