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(''); }); 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(); }); }); });