refactor(core): 移除 UI 交互层,保持 core 为纯能力模块
- 删除 terminal.ts 及其测试(UI 交互应属于 cli 包) - 清理 index.ts 中的 CLI 入口代码 - 合并 lib.ts 导出到 index.ts - 移除 commander、inquirer 依赖 - 更新 package.json 导出配置
This commit is contained in:
@@ -1,282 +0,0 @@
|
||||
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<typeof vi.spyOn>;
|
||||
let stdoutWriteSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user