From c09b870fe70bbb09932a3b21967645001b89ad4e Mon Sep 17 00:00:00 2001 From: weishu Date: Wed, 24 Dec 2025 13:34:55 +0800 Subject: [PATCH] refactor(test): simplify keyboard input handling and extract type definitions Remove reliance on key.name and key.sequence in favor of using input directly. Extract TTY type definitions for clarity and add scoping constraints to prevent variable shadowing. Update AGENTS.md documentation for accuracy. --- AGENTS.md | 11 +++--- cli/src/ui/ink/useSwitchControls.test.ts | 43 ++++++++++++------------ cli/src/ui/ink/useSwitchControls.ts | 5 ++- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7f806235..a387ab2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,19 +15,16 @@ Short guide for AI agents in this repo. Prefer progressive loading: start with t - `localdocs/` (optional deep dives) ## Shared rules +- No backward compatibility: breaking old format freely. - TypeScript strict; no untyped code. - Bun workspaces; run `bun` commands from repo root. - Path alias `@/*` maps to `./src/*` per package. -- No backward compatibility: breaking format changes are allowed. - Prefer 4-space indentation. ## Common commands (repo root) - bun run build - bun run build:single-exe - bun run typecheck - bun run dev:server - bun run dev:web - bun run test + +- `bun typecheck` +- `bun run test` ## Key source dirs - `cli/src/api/`, `cli/src/claude/`, `cli/src/commands/`, `cli/src/codex/` diff --git a/cli/src/ui/ink/useSwitchControls.test.ts b/cli/src/ui/ink/useSwitchControls.test.ts index d9ef1542..5e6a3164 100644 --- a/cli/src/ui/ink/useSwitchControls.test.ts +++ b/cli/src/ui/ink/useSwitchControls.test.ts @@ -6,8 +6,6 @@ import { useSwitchControls, type ConfirmationMode, type ActionInProgress } from type Key = { ctrl?: boolean; - name?: string; - sequence?: string; }; type SwitchState = { @@ -27,24 +25,24 @@ vi.mock('ink', async () => { }; }); +type TtyWriteStream = NodeJS.WriteStream & { + isTTY?: boolean; + columns?: number; + rows?: number; +}; + +type TtyReadStream = NodeJS.ReadStream & { + isTTY?: boolean; +}; + const createInkStreams = (): { stdout: NodeJS.WriteStream; stderr: NodeJS.WriteStream; stdin: NodeJS.ReadStream; } => { - const stdout = new PassThrough() as NodeJS.WriteStream & { - isTTY?: boolean; - columns?: number; - rows?: number; - }; - const stderr = new PassThrough() as NodeJS.WriteStream & { - isTTY?: boolean; - columns?: number; - rows?: number; - }; - const stdin = new PassThrough() as NodeJS.ReadStream & { - isTTY?: boolean; - }; + const stdout = new PassThrough() as unknown as TtyWriteStream; + const stderr = new PassThrough() as unknown as TtyWriteStream; + const stdin = new PassThrough() as unknown as TtyReadStream; Object.assign(stdout, { isTTY: true, columns: 80, rows: 24 }); Object.assign(stderr, { isTTY: true, columns: 80, rows: 24 }); @@ -143,10 +141,11 @@ describe('useSwitchControls', () => { vi.runOnlyPendingTimers(); }); if (renderer) { + const activeRenderer = renderer; await act(async () => { - renderer.unmount(); + activeRenderer.unmount(); }); - renderer.cleanup(); + activeRenderer.cleanup(); renderer = null; } vi.useRealTimers(); @@ -158,7 +157,7 @@ describe('useSwitchControls', () => { const killSpy = vi.spyOn(process, 'kill').mockImplementation(() => true); await mount({ onSwitch }); - await triggerInput(' ', { name: 'space' }); + await triggerInput(' ', {}); expect(latestState?.confirmationMode).toBe('switch'); expect(latestState?.actionInProgress).toBe(null); expect(onSwitch).not.toHaveBeenCalled(); @@ -187,10 +186,10 @@ describe('useSwitchControls', () => { const onSwitch = vi.fn(); await mount({ onSwitch }); - await triggerInput(' ', { name: 'space' }); + await triggerInput(' ', {}); expect(latestState?.confirmationMode).toBe('switch'); - await triggerInput('', { sequence: '\u001b[1:3u' }); + await triggerInput('\u001b[1:3u', {}); expect(latestState?.confirmationMode).toBe('switch'); expect(latestState?.actionInProgress).toBe(null); }); @@ -199,7 +198,7 @@ describe('useSwitchControls', () => { const onSwitch = vi.fn(); await mount({ onSwitch }); - await triggerInput('', { sequence: '\u001b[3:3u', name: 'space' }); + await triggerInput('\u001b[3:3u', {}); expect(onSwitch).not.toHaveBeenCalled(); expect(latestState?.confirmationMode).toBe(null); }); @@ -208,7 +207,7 @@ describe('useSwitchControls', () => { const onSwitch = vi.fn(); await mount({ onSwitch }); - await triggerInput(' ', { name: 'space' }); + await triggerInput(' ', {}); expect(latestState?.confirmationMode).toBe('switch'); await advanceTimers(5000); diff --git a/cli/src/ui/ink/useSwitchControls.ts b/cli/src/ui/ink/useSwitchControls.ts index e4cdcee1..689609b7 100644 --- a/cli/src/ui/ink/useSwitchControls.ts +++ b/cli/src/ui/ink/useSwitchControls.ts @@ -72,9 +72,8 @@ export function useSwitchControls(opts: { return; } - const sequence = typeof key.sequence === 'string' ? key.sequence : input; - const isKeyRelease = typeof sequence === 'string' && /^\u001b\[[0-9;]*:3u$/.test(sequence); - const isSpace = Boolean(onSwitch) && !isKeyRelease && (input === ' ' || key.name === 'space'); + const isKeyRelease = /^\u001b\[[0-9;]*:3u$/.test(input); + const isSpace = Boolean(onSwitch) && !isKeyRelease && input === ' '; const hasPrintableInput = typeof input === 'string' && input.length > 0; if (isSpace) {