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.
124 lines
2.9 KiB
TypeScript
124 lines
2.9 KiB
TypeScript
import { IPty, PtyEvent, IDisposable } from '../../src/types'
|
|
|
|
/**
|
|
* Mock IPty for testing.
|
|
*
|
|
* Implements the full IPty interface with manual trigger methods
|
|
* (emitData, emitExit) and recording of calls (writes, resizes, killed).
|
|
*
|
|
* All properties are mutable for testing convenience.
|
|
*/
|
|
export interface MockIPty extends IPty {
|
|
/** Recorded write() calls. */
|
|
writes: string[]
|
|
/** Recorded resize() calls. */
|
|
resizes: Array<{ cols: number; rows: number }>
|
|
/** Whether kill() was called. */
|
|
killed: boolean
|
|
/** Manually trigger onData listeners. */
|
|
emitData(data: string): void
|
|
/** Manually trigger onExit listeners. */
|
|
emitExit(exitCode: number, signal?: number): void
|
|
}
|
|
|
|
/**
|
|
* Create a mock PTY for testing.
|
|
*
|
|
* pid, cols, rows are fixed but can be overridden via options for specific tests.
|
|
*/
|
|
export function createMockPty(options?: {
|
|
pid?: number
|
|
cols?: number
|
|
rows?: number
|
|
}): MockIPty {
|
|
const pid = options?.pid ?? 12345
|
|
let cols = options?.cols ?? 80
|
|
let rows = options?.rows ?? 24
|
|
|
|
const writes: string[] = []
|
|
const resizes: Array<{ cols: number; rows: number }> = []
|
|
let killed = false
|
|
|
|
// Event listeners: store arrays of listeners for each event type
|
|
const dataListeners: Array<(data: string) => void> = []
|
|
const exitListeners: Array<(event: { exitCode: number; signal?: number }) => void> = []
|
|
|
|
const onData: PtyEvent<string> = (listener: (e: string) => void): IDisposable => {
|
|
dataListeners.push(listener)
|
|
return {
|
|
dispose() {
|
|
const index = dataListeners.indexOf(listener)
|
|
if (index !== -1) {
|
|
dataListeners.splice(index, 1)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
const onExit: PtyEvent<{ exitCode: number; signal?: number }> = (
|
|
listener: (e: { exitCode: number; signal?: number }) => void,
|
|
): IDisposable => {
|
|
exitListeners.push(listener)
|
|
return {
|
|
dispose() {
|
|
const index = exitListeners.indexOf(listener)
|
|
if (index !== -1) {
|
|
exitListeners.splice(index, 1)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
const write = (data: string): void => {
|
|
writes.push(data)
|
|
}
|
|
|
|
const resize = (newCols: number, newRows: number): void => {
|
|
cols = newCols
|
|
rows = newRows
|
|
resizes.push({ cols: newCols, rows: newRows })
|
|
}
|
|
|
|
const kill = (signal?: string): void => {
|
|
killed = true
|
|
}
|
|
|
|
const emitData = (data: string): void => {
|
|
for (const listener of dataListeners) {
|
|
listener(data)
|
|
}
|
|
}
|
|
|
|
const emitExit = (exitCode: number, signal?: number): void => {
|
|
const event: { exitCode: number; signal?: number } = { exitCode }
|
|
if (signal !== undefined) {
|
|
event.signal = signal
|
|
}
|
|
for (const listener of exitListeners) {
|
|
listener(event)
|
|
}
|
|
}
|
|
|
|
return {
|
|
pid,
|
|
get cols() {
|
|
return cols
|
|
},
|
|
get rows() {
|
|
return rows
|
|
},
|
|
onData,
|
|
onExit,
|
|
write,
|
|
resize,
|
|
kill,
|
|
writes,
|
|
resizes,
|
|
get killed() {
|
|
return killed
|
|
},
|
|
emitData,
|
|
emitExit,
|
|
}
|
|
}
|