Files
ai-terminal-assistant/tests/unit/lsp/index.test.ts
T
kurihada bca19b7741 test: 补充单元测试提升代码覆盖率
新增测试文件:
- agent/executor-extended.test.ts, presets/
- context/manager-extended.test.ts
- core/agent.test.ts, providers.test.ts
- lsp/cli.test.ts, client-extended.test.ts, index.test.ts
- permission/file-prompt.test.ts, prompt.test.ts
- skills/builtin/
- tools/filesystem/write_file-extended.test.ts
- tools/git/git_commit-extended.test.ts
- tools/load_description.test.ts
- tools/todo/todo-manager.test.ts
- tools/tool-search.test.ts
- types/
- utils/config-extended.test.ts, diff-extended.test.ts

修改现有测试:
- agent/manager.test.ts
- tools/skill/skill.test.ts
- utils/config.test.ts, diff.test.ts, image.test.ts
2025-12-11 20:37:03 +08:00

347 lines
10 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock functions
const mockTouchFile = vi.fn();
const mockGetDiagnostics = vi.fn();
const mockGetFileDiagnostics = vi.fn();
const mockShutdown = vi.fn();
// 创建 mock class
class MockLSPClientManager {
touchFile = mockTouchFile;
getDiagnostics = mockGetDiagnostics;
getFileDiagnostics = mockGetFileDiagnostics;
shutdown = mockShutdown;
constructor(_rootPath?: string) {}
}
// Mock client module
vi.mock('../../../src/lsp/client.js', () => ({
LSPClientManager: MockLSPClientManager,
}));
// Mock language module
vi.mock('../../../src/lsp/language.js', () => ({
getLanguageId: vi.fn((path: string) => {
if (path.endsWith('.ts')) return 'typescript';
if (path.endsWith('.py')) return 'python';
return undefined;
}),
isLanguageSupported: vi.fn((path: string) => {
return path.endsWith('.ts') || path.endsWith('.py');
}),
getSupportedExtensions: vi.fn(() => ['.ts', '.js', '.py']),
}));
// Mock server module
vi.mock('../../../src/lsp/server.js', () => ({
getServerConfig: vi.fn(),
hasServerConfig: vi.fn(),
getSupportedLanguages: vi.fn(() => []),
}));
describe('LSP Index - LSP 模块入口', () => {
let lspModule: typeof import('../../../src/lsp/index.js');
beforeEach(async () => {
vi.clearAllMocks();
// 重置模块缓存以获取干净的状态
vi.resetModules();
// 重新导入模块
lspModule = await import('../../../src/lsp/index.js');
});
describe('initLSP - 初始化 LSP', () => {
it('初始化 LSP 管理器', () => {
lspModule.initLSP('/test/project');
expect(lspModule.getLSPManager()).not.toBeNull();
});
it('重复初始化不会创建新实例', () => {
lspModule.initLSP('/test/project');
const manager1 = lspModule.getLSPManager();
lspModule.initLSP('/another/project');
const manager2 = lspModule.getLSPManager();
expect(manager1).toBe(manager2);
});
it('默认使用 process.cwd()', () => {
lspModule.initLSP();
expect(lspModule.getLSPManager()).not.toBeNull();
});
});
describe('getLSPManager - 获取管理器', () => {
it('未初始化时返回 null', async () => {
// 重置模块获取干净状态
vi.resetModules();
const freshModule = await import('../../../src/lsp/index.js');
expect(freshModule.getLSPManager()).toBeNull();
});
it('初始化后返回管理器实例', () => {
lspModule.initLSP();
expect(lspModule.getLSPManager()).not.toBeNull();
});
});
describe('touchFile - 通知文件变更', () => {
it('未初始化时返回 false', async () => {
vi.resetModules();
const freshModule = await import('../../../src/lsp/index.js');
const result = await freshModule.touchFile('/test/file.ts');
expect(result).toBe(false);
});
it('不支持的语言返回 false', async () => {
lspModule.initLSP();
const result = await lspModule.touchFile('/test/file.txt');
expect(result).toBe(false);
});
it('支持的语言调用管理器', async () => {
mockTouchFile.mockResolvedValue(true);
lspModule.initLSP();
const result = await lspModule.touchFile('/test/file.ts');
expect(mockTouchFile).toHaveBeenCalledWith('/test/file.ts', false);
expect(result).toBe(true);
});
it('新文件传递 isNew 参数', async () => {
mockTouchFile.mockResolvedValue(true);
lspModule.initLSP();
await lspModule.touchFile('/test/file.ts', true);
expect(mockTouchFile).toHaveBeenCalledWith('/test/file.ts', true);
});
it('touchFile 出错时静默返回 false', async () => {
mockTouchFile.mockRejectedValue(new Error('LSP error'));
lspModule.initLSP();
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const result = await lspModule.touchFile('/test/file.ts');
expect(result).toBe(false);
consoleErrorSpy.mockRestore();
});
});
describe('getDiagnostics - 获取所有诊断', () => {
it('未初始化时返回空 Map', async () => {
vi.resetModules();
const freshModule = await import('../../../src/lsp/index.js');
const result = freshModule.getDiagnostics();
expect(result).toBeInstanceOf(Map);
expect(result.size).toBe(0);
});
it('返回管理器的诊断信息', () => {
const mockDiagnostics = new Map([
['/test/file.ts', [{ file: '/test/file.ts', line: 1, column: 1, severity: 'error', message: 'Test error' }]],
]);
mockGetDiagnostics.mockReturnValue(mockDiagnostics);
lspModule.initLSP();
const result = lspModule.getDiagnostics();
expect(result).toBe(mockDiagnostics);
});
});
describe('getFileDiagnostics - 获取单文件诊断', () => {
it('未初始化时返回空数组', async () => {
vi.resetModules();
const freshModule = await import('../../../src/lsp/index.js');
const result = freshModule.getFileDiagnostics('/test/file.ts');
expect(result).toEqual([]);
});
it('返回指定文件的诊断', () => {
const mockDiagnostics = [
{ file: '/test/file.ts', line: 1, column: 1, severity: 'error' as const, message: 'Error' },
];
mockGetFileDiagnostics.mockReturnValue(mockDiagnostics);
lspModule.initLSP();
const result = lspModule.getFileDiagnostics('/test/file.ts');
expect(result).toBe(mockDiagnostics);
});
});
describe('formatDiagnostics - 格式化诊断信息', () => {
it('空诊断返回空字符串', () => {
const result = lspModule.formatDiagnostics([]);
expect(result).toBe('');
});
it('格式化单个诊断', () => {
const diagnostics = [
{
file: '/test/file.ts',
line: 10,
column: 5,
severity: 'error' as const,
message: 'Type error',
code: 'TS2345',
source: 'typescript',
},
];
const result = lspModule.formatDiagnostics(diagnostics);
expect(result).toContain('10:5');
expect(result).toContain('ERROR');
expect(result).toContain('TS2345');
expect(result).toContain('Type error');
expect(result).toContain('typescript');
});
it('格式化多个诊断', () => {
const diagnostics = [
{ file: '/test/file.ts', line: 1, column: 1, severity: 'error' as const, message: 'Error 1' },
{ file: '/test/file.ts', line: 2, column: 2, severity: 'warning' as const, message: 'Warning 1' },
];
const result = lspModule.formatDiagnostics(diagnostics);
expect(result).toContain('Error 1');
expect(result).toContain('Warning 1');
expect(result).toContain('ERROR');
expect(result).toContain('WARNING');
});
it('无 code 时不显示 code', () => {
const diagnostics = [
{ file: '/test/file.ts', line: 1, column: 1, severity: 'error' as const, message: 'Error' },
];
const result = lspModule.formatDiagnostics(diagnostics);
expect(result).not.toContain('[');
});
it('无 source 时不显示 source', () => {
const diagnostics = [
{ file: '/test/file.ts', line: 1, column: 1, severity: 'error' as const, message: 'Error' },
];
const result = lspModule.formatDiagnostics(diagnostics);
expect(result).not.toContain('(');
});
});
describe('getFormattedFileDiagnostics - 获取格式化的文件诊断', () => {
it('无错误和警告时返回空字符串', async () => {
mockGetFileDiagnostics.mockReturnValue([
{ file: '/test/file.ts', line: 1, column: 1, severity: 'info', message: 'Info' },
{ file: '/test/file.ts', line: 2, column: 2, severity: 'hint', message: 'Hint' },
]);
lspModule.initLSP();
const result = await lspModule.getFormattedFileDiagnostics('/test/file.ts');
expect(result).toBe('');
});
it('只显示错误和警告', async () => {
mockGetFileDiagnostics.mockReturnValue([
{ file: '/test/file.ts', line: 1, column: 1, severity: 'error', message: 'Error' },
{ file: '/test/file.ts', line: 2, column: 2, severity: 'warning', message: 'Warning' },
{ file: '/test/file.ts', line: 3, column: 3, severity: 'info', message: 'Info' },
]);
lspModule.initLSP();
const result = await lspModule.getFormattedFileDiagnostics('/test/file.ts');
expect(result).toContain('Error');
expect(result).toContain('Warning');
expect(result).not.toContain('Info');
});
it('包含 file_diagnostics 标签', async () => {
mockGetFileDiagnostics.mockReturnValue([
{ file: '/test/file.ts', line: 1, column: 1, severity: 'error', message: 'Error' },
]);
lspModule.initLSP();
const result = await lspModule.getFormattedFileDiagnostics('/test/file.ts');
expect(result).toContain('<file_diagnostics');
expect(result).toContain('/test/file.ts');
expect(result).toContain('</file_diagnostics>');
});
it('显示错误和警告数量', async () => {
mockGetFileDiagnostics.mockReturnValue([
{ file: '/test/file.ts', line: 1, column: 1, severity: 'error', message: 'Error 1' },
{ file: '/test/file.ts', line: 2, column: 2, severity: 'error', message: 'Error 2' },
{ file: '/test/file.ts', line: 3, column: 3, severity: 'warning', message: 'Warning' },
]);
lspModule.initLSP();
const result = await lspModule.getFormattedFileDiagnostics('/test/file.ts');
expect(result).toContain('2 个错误');
expect(result).toContain('1 个警告');
});
});
describe('shutdownLSP - 关闭 LSP', () => {
it('关闭管理器', async () => {
lspModule.initLSP();
await lspModule.shutdownLSP();
expect(mockShutdown).toHaveBeenCalled();
expect(lspModule.getLSPManager()).toBeNull();
});
it('未初始化时安全调用', async () => {
vi.resetModules();
const freshModule = await import('../../../src/lsp/index.js');
await expect(freshModule.shutdownLSP()).resolves.not.toThrow();
});
});
describe('导出', () => {
it('导出 getLanguageId', () => {
expect(lspModule.getLanguageId).toBeDefined();
});
it('导出 isLanguageSupported', () => {
expect(lspModule.isLanguageSupported).toBeDefined();
});
it('导出 getSupportedExtensions', () => {
expect(lspModule.getSupportedExtensions).toBeDefined();
});
it('导出 LSPClientManager', () => {
expect(lspModule.LSPClientManager).toBeDefined();
});
});
});