Files
web-terminal/test/helpers/mock-pty.ts
Yaojia Wang 0fa0b69ce9 feat: W1 batch — config, protocol, ring-buffer, origin, mock-pty (TDD)
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.
2026-06-16 08:09:40 +02:00

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,
}
}