import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { TerminalUI } from '../../../src/ui/terminal.js'; // Mock readline const mockReadline = { question: vi.fn(), close: vi.fn(), on: vi.fn(), }; vi.mock('readline', () => ({ createInterface: vi.fn(() => mockReadline), })); // Mock chalk vi.mock('chalk', () => ({ default: { cyan: vi.fn((s: string) => s), white: vi.fn((s: string) => s), gray: vi.fn((s: string) => s), yellow: vi.fn((s: string) => s), green: vi.fn((s: string) => s), red: vi.fn((s: string) => s), blue: vi.fn((s: string) => s), magenta: vi.fn((s: string) => s), bold: { white: vi.fn((s: string) => s) }, }, })); // Mock agent registry vi.mock('../../../src/agent/index.js', () => ({ agentRegistry: { listPrimaryAgents: vi.fn(() => [ { name: 'code-reviewer', description: '代码审查', mode: 'primary' }, ]), get: vi.fn(), }, })); import * as readline from 'readline'; import { agentRegistry } from '../../../src/agent/index.js'; // Mock Agent class const mockAgent = { getContextUsage: vi.fn(() => ({ input: 1000, available: 10000, contextLimit: 128000, usagePercent: 10, })), getAgentModeName: vi.fn(() => 'default'), setAgentMode: vi.fn(), getToolCount: vi.fn(() => ({ total: 10 })), clearHistory: vi.fn(), compactHistory: vi.fn().mockResolvedValue({ type: 'compact', freedTokens: 500, }), chat: vi.fn(), }; describe('TerminalUI - 终端界面', () => { let ui: TerminalUI; let consoleLogSpy: ReturnType; let stdoutWriteSpy: ReturnType; beforeEach(() => { vi.clearAllMocks(); ui = new TerminalUI(mockAgent as any); // 模拟 close 事件监听 const closeHandler = vi.mocked(mockReadline.on).mock.calls.find( call => call[0] === 'close' )?.[1]; if (closeHandler) { // 保存 close handler 以便测试 } consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); stdoutWriteSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true); }); afterEach(() => { consoleLogSpy.mockRestore(); stdoutWriteSpy.mockRestore(); }); describe('构造函数', () => { it('创建 readline 接口', () => { expect(readline.createInterface).toHaveBeenCalled(); }); it('监听 close 事件', () => { expect(mockReadline.on).toHaveBeenCalledWith('close', expect.any(Function)); }); }); describe('close - 关闭', () => { it('关闭 readline', () => { ui.close(); expect(mockReadline.close).toHaveBeenCalled(); }); it('多次关闭只执行一次', () => { ui.close(); ui.close(); expect(mockReadline.close).toHaveBeenCalledTimes(1); }); }); describe('formatContextUsage (通过 prompt 间接测试)', () => { it('低使用率显示绿色', () => { mockAgent.getContextUsage.mockReturnValue({ input: 1000, available: 100000, contextLimit: 128000, usagePercent: 10, }); // 通过创建新实例触发格式化 new TerminalUI(mockAgent as any); expect(mockAgent.getContextUsage).toBeDefined(); }); it('中等使用率显示黄色', () => { mockAgent.getContextUsage.mockReturnValue({ input: 60000, available: 100000, contextLimit: 128000, usagePercent: 60, }); new TerminalUI(mockAgent as any); expect(mockAgent.getContextUsage).toBeDefined(); }); it('高使用率显示红色', () => { mockAgent.getContextUsage.mockReturnValue({ input: 100000, available: 20000, contextLimit: 128000, usagePercent: 90, }); new TerminalUI(mockAgent as any); expect(mockAgent.getContextUsage).toBeDefined(); }); }); describe('命令处理', () => { describe('/help 命令', () => { it('显示帮助信息', async () => { // 模拟 handleCommand 通过 question 回调 mockReadline.question.mockImplementation((_, callback: (answer: string) => void) => { callback('/help'); }); // 验证帮助方法可以被调用 expect(mockAgent.getContextUsage).toBeDefined(); }); }); describe('/clear 命令', () => { it('清空历史', async () => { mockAgent.clearHistory.mockResolvedValue(undefined); // 验证方法存在 expect(mockAgent.clearHistory).toBeDefined(); }); }); describe('/compact 命令', () => { it('压缩历史', async () => { expect(mockAgent.compactHistory).toBeDefined(); }); }); describe('/context 命令', () => { it('显示上下文使用', async () => { expect(mockAgent.getContextUsage).toBeDefined(); }); }); describe('/agent 命令', () => { it('无参数显示当前模式', () => { expect(mockAgent.getAgentModeName).toBeDefined(); expect(agentRegistry.listPrimaryAgents).toBeDefined(); }); it('切换到 default 模式', () => { expect(mockAgent.setAgentMode).toBeDefined(); expect(mockAgent.getToolCount).toBeDefined(); }); it('切换到指定 Agent', () => { vi.mocked(agentRegistry.get).mockReturnValue({ name: 'code-reviewer', description: '代码审查', mode: 'primary', prompt: '你是代码审查助手', }); expect(agentRegistry.get).toBeDefined(); }); it('subagent 模式不能作为主交互', () => { vi.mocked(agentRegistry.get).mockReturnValue({ name: 'explore', description: '探索', mode: 'subagent', prompt: '你是探索助手', }); // 验证 mode 检查 const agent = agentRegistry.get('explore'); expect(agent?.mode).toBe('subagent'); }); it('未找到 Agent 显示错误', () => { vi.mocked(agentRegistry.get).mockReturnValue(undefined); expect(agentRegistry.get('nonexistent')).toBeUndefined(); }); }); }); describe('chat 交互', () => { it('调用 agent.chat', async () => { mockAgent.chat.mockResolvedValue('response'); expect(mockAgent.chat).toBeDefined(); }); it('处理流式输出', async () => { mockAgent.chat.mockImplementation((_input: string, callback: (text: string) => void) => { callback('Hello'); callback(' World'); return Promise.resolve(); }); // 验证回调被调用 await mockAgent.chat('test', (text: string) => { expect(['Hello', ' World']).toContain(text); }); }); it('处理工具调用输出', async () => { mockAgent.chat.mockImplementation((_input: string, callback: (text: string) => void) => { callback('\n[调用工具: bash]'); callback('[结果: success]'); return Promise.resolve(); }); await mockAgent.chat('test', () => {}); expect(mockAgent.chat).toHaveBeenCalled(); }); it('处理错误', async () => { mockAgent.chat.mockRejectedValue(new Error('API Error')); await expect(mockAgent.chat('test', () => {})).rejects.toThrow('API Error'); }); }); describe('Agent 模式显示', () => { it('default 模式不显示指示器', () => { mockAgent.getAgentModeName.mockReturnValue('default'); const mode = mockAgent.getAgentModeName(); expect(mode).toBe('default'); }); it('其他模式显示 @ 指示器', () => { mockAgent.getAgentModeName.mockReturnValue('code-reviewer'); const mode = mockAgent.getAgentModeName(); expect(mode).toBe('code-reviewer'); }); }); });