Parallel module-builder subagents, disjoint file ownership, shared tree: - T4 src/config.ts — loadConfig+M1 origins (32 tests) - T5 src/protocol.ts — parse/serialize+SESSION_ID_RE+M7 (60 tests) - T6 src/session/ring-buffer.ts — byte-accurate, no UTF-8/ANSI split+M2 (15 tests) - T7 src/http/origin.ts — host+port match, undefined rejected (12 tests) - T3 test/helpers/mock-pty.ts — IPty double w/ emitData/emitExit (9 tests) Verified by orchestrator: tsc --noEmit clean; full suite 128 tests pass.
120 lines
2.8 KiB
TypeScript
120 lines
2.8 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { createMockPty } from './mock-pty'
|
|
|
|
describe('createMockPty', () => {
|
|
it('emitData triggers all onData listeners', () => {
|
|
const pty = createMockPty()
|
|
const listener1Results: string[] = []
|
|
const listener2Results: string[] = []
|
|
|
|
pty.onData((data) => {
|
|
listener1Results.push(data)
|
|
})
|
|
pty.onData((data) => {
|
|
listener2Results.push(data)
|
|
})
|
|
|
|
pty.emitData('hello')
|
|
pty.emitData(' world')
|
|
|
|
expect(listener1Results).toEqual(['hello', ' world'])
|
|
expect(listener2Results).toEqual(['hello', ' world'])
|
|
})
|
|
|
|
it('onData returns a disposable that unsubscribes', () => {
|
|
const pty = createMockPty()
|
|
const results: string[] = []
|
|
|
|
const disposable = pty.onData((data) => {
|
|
results.push(data)
|
|
})
|
|
|
|
pty.emitData('first')
|
|
disposable.dispose()
|
|
pty.emitData('second')
|
|
|
|
expect(results).toEqual(['first'])
|
|
})
|
|
|
|
it('records write calls', () => {
|
|
const pty = createMockPty()
|
|
|
|
pty.write('input 1')
|
|
pty.write('input 2')
|
|
|
|
expect(pty.writes).toEqual(['input 1', 'input 2'])
|
|
})
|
|
|
|
it('records resize calls', () => {
|
|
const pty = createMockPty()
|
|
|
|
pty.resize(100, 50)
|
|
pty.resize(120, 60)
|
|
|
|
expect(pty.resizes).toEqual([
|
|
{ cols: 100, rows: 50 },
|
|
{ cols: 120, rows: 60 },
|
|
])
|
|
})
|
|
|
|
it('emitExit triggers onExit listeners', () => {
|
|
const pty = createMockPty()
|
|
const results: Array<{ exitCode: number; signal?: number }> = []
|
|
|
|
pty.onExit((event) => {
|
|
results.push(event)
|
|
})
|
|
|
|
pty.emitExit(0)
|
|
pty.emitExit(1, 9)
|
|
|
|
expect(results).toEqual([
|
|
{ exitCode: 0 },
|
|
{ exitCode: 1, signal: 9 },
|
|
])
|
|
})
|
|
|
|
it('onExit returns a disposable that unsubscribes', () => {
|
|
const pty = createMockPty()
|
|
const results: Array<{ exitCode: number; signal?: number }> = []
|
|
|
|
const disposable = pty.onExit((event) => {
|
|
results.push(event)
|
|
})
|
|
|
|
pty.emitExit(0)
|
|
disposable.dispose()
|
|
pty.emitExit(1)
|
|
|
|
expect(results).toEqual([{ exitCode: 0 }])
|
|
})
|
|
|
|
it('kill marks killed flag', () => {
|
|
const pty = createMockPty()
|
|
|
|
expect(pty.killed).toBe(false)
|
|
pty.kill()
|
|
expect(pty.killed).toBe(true)
|
|
})
|
|
|
|
it('kill can accept an optional signal', () => {
|
|
const pty = createMockPty()
|
|
|
|
pty.kill('SIGTERM')
|
|
expect(pty.killed).toBe(true)
|
|
})
|
|
|
|
it('has IPty interface properties', () => {
|
|
const pty = createMockPty()
|
|
|
|
expect(typeof pty.pid).toBe('number')
|
|
expect(typeof pty.cols).toBe('number')
|
|
expect(typeof pty.rows).toBe('number')
|
|
expect(typeof pty.onData).toBe('function')
|
|
expect(typeof pty.onExit).toBe('function')
|
|
expect(typeof pty.write).toBe('function')
|
|
expect(typeof pty.resize).toBe('function')
|
|
expect(typeof pty.kill).toBe('function')
|
|
})
|
|
})
|