Skip to main content
Glama
languageServer.test.ts10.7 kB
import { describe, it, expect, beforeEach, vi } from 'vitest' import { LanguageServerManager } from '../../src/languageServerManager' import { LanguageServerService } from '../../src/languageServerService' import type { LanguageServerConfig, MonorepoProject } from '../../src/types/languageServer' import * as fs from 'fs/promises' import { existsSync } from 'fs' // Mock the fs module vi.mock('fs/promises') vi.mock('fs', () => ({ existsSync: vi.fn(), })) describe('LanguageServerManager', () => { let manager: LanguageServerManager let mockConfig: LanguageServerConfig beforeEach(() => { vi.clearAllMocks() mockConfig = { monorepoRoot: '/test/monorepo', projects: [], } manager = new LanguageServerManager(mockConfig) }) describe('discoverProjects', () => { it('should discover TypeScript projects in monorepo', async () => { // Mock file system structure vi.mocked(fs.readdir).mockImplementation((dir, _options) => { if (dir === '/test/monorepo') { return Promise.resolve([ { name: 'package-a', isDirectory: () => true }, { name: 'package-b', isDirectory: () => true }, { name: 'node_modules', isDirectory: () => true }, { name: '.git', isDirectory: () => true }, ] as any) } else if (dir === '/test/monorepo/package-a') { return Promise.resolve([ { name: 'tsconfig.json', isDirectory: () => false }, { name: 'package.json', isDirectory: () => false }, ] as any) } else if (dir === '/test/monorepo/package-b') { return Promise.resolve([ { name: 'tsconfig.json', isDirectory: () => false }, { name: 'package.json', isDirectory: () => false }, ] as any) } return Promise.resolve([]) }) vi.mocked(fs.readFile).mockImplementation((filePath) => { const pathStr = filePath as string if (pathStr.includes('package-a/package.json')) { return Promise.resolve( JSON.stringify({ name: 'package-a', dependencies: {}, }) ) } else if (pathStr.includes('package-b/package.json')) { return Promise.resolve( JSON.stringify({ name: 'package-b', devDependencies: { '@sveltejs/kit': '^1.0.0', }, }) ) } return Promise.resolve('{}') }) const projects = await manager.discoverProjects() expect(projects).toHaveLength(2) expect(projects[0]).toEqual({ name: 'package-a', path: 'package-a', type: 'node', tsconfigPath: 'tsconfig.json', rootDir: '/test/monorepo/package-a', }) expect(projects[1]).toEqual({ name: 'package-b', path: 'package-b', type: 'sveltekit', tsconfigPath: 'tsconfig.json', rootDir: '/test/monorepo/package-b', }) }) it('should skip hidden directories and node_modules', async () => { vi.mocked(fs.readdir).mockResolvedValue([ { name: '.hidden', isDirectory: () => true }, { name: 'node_modules', isDirectory: () => true }, { name: 'valid-package', isDirectory: () => true }, ] as any) await manager.discoverProjects() // Should only process valid-package expect(fs.readdir).toHaveBeenCalledWith('/test/monorepo', { withFileTypes: true }) expect(fs.readdir).not.toHaveBeenCalledWith( '/test/monorepo/.hidden', expect.any(Object) ) expect(fs.readdir).not.toHaveBeenCalledWith( '/test/monorepo/node_modules', expect.any(Object) ) }) }) describe('getProjectForFile', () => { it('should return undefined for files outside any project', () => { const result = manager.getProjectForFile('/outside/file.ts') expect(result).toBeUndefined() }) }) }) describe('LanguageServerService', () => { let service: LanguageServerService beforeEach(() => { vi.clearAllMocks() service = new LanguageServerService() }) describe('initialize', () => { it('should initialize with auto-discovery', async () => { vi.mocked(fs.readdir).mockImplementation((dir, _options) => { if (dir === '/test/monorepo') { return Promise.resolve([{ name: 'project1', isDirectory: () => true }] as any) } else if (dir === '/test/monorepo/project1') { return Promise.resolve([ { name: 'tsconfig.json', isDirectory: () => false }, { name: 'package.json', isDirectory: () => false }, ] as any) } return Promise.resolve([]) }) vi.mocked(fs.readFile).mockImplementation((filePath) => { const pathStr = filePath as string if (pathStr.includes('package.json')) { return Promise.resolve(JSON.stringify({ name: 'project1' })) } else if (pathStr.includes('tsconfig.json')) { return Promise.resolve( JSON.stringify({ compilerOptions: { target: 'es2020', module: 'commonjs', skipLibCheck: true, noLib: true, types: [], typeRoots: [], noResolve: true, isolatedModules: true, }, exclude: ['node_modules', '**/*.spec.ts', '**/*.test.ts'], }) ) } return Promise.resolve('{}') }) vi.mocked(existsSync).mockReturnValue(true) const result = await service.initialize('/test/monorepo', true) expect(result.projects).toHaveLength(1) expect(result.projects[0]?.name).toBe('project1') }) it('should initialize without auto-discovery', async () => { const result = await service.initialize('/test/monorepo', false) expect(result.projects).toHaveLength(0) }) }) describe('configureProject', () => { it('should add a new project configuration', async () => { vi.mocked(fs.readFile).mockResolvedValue( JSON.stringify({ compilerOptions: { target: 'es2020', module: 'commonjs', skipLibCheck: true, noLib: true, types: [], typeRoots: [], noResolve: true, isolatedModules: true, }, exclude: ['node_modules', '**/*.spec.ts', '**/*.test.ts'], }) ) vi.mocked(existsSync).mockReturnValue(true) await service.initialize('/test/monorepo', false) const project: MonorepoProject = { name: 'test-project', path: 'packages/test', type: 'node', tsconfigPath: 'tsconfig.json', rootDir: '/test/monorepo/packages/test', } await service.configureProject(project) // Project should be added successfully expect(service.isInitialized()).toBe(true) }) }) describe('getDiagnostics', () => { it('should return empty diagnostics when not initialized', () => { const result = service.getDiagnostics({}) expect(result).toEqual([]) }) it('should filter diagnostics by severity', async () => { vi.mocked(fs.readFile).mockResolvedValue('{}') vi.mocked(existsSync).mockReturnValue(true) await service.initialize('/test/monorepo', false) // Since we're mocking, we can't test actual TypeScript diagnostics // but we can test the filtering logic const result = service.getDiagnostics({ severity: 'error' }) expect(result).toBeDefined() }) }) describe('getCompletions', () => { it('should return empty completions when not initialized', () => { const result = service.getCompletions('/test/file.ts', 1, 1) expect(result).toEqual({ completions: [] }) }) }) describe('getHover', () => { it('should return undefined when not initialized', () => { const result = service.getHover('/test/file.ts', 1, 1) expect(result).toBeUndefined() }) }) describe('getDefinition', () => { it('should return undefined when not initialized', () => { const result = service.getDefinition('/test/file.ts', 1, 1) expect(result).toBeUndefined() }) }) describe('findReferences', () => { it('should return empty references when not initialized', () => { const result = service.findReferences('/test/file.ts', 1, 1) expect(result).toEqual({ references: [] }) }) }) describe('renameSymbol', () => { it('should return empty changes when not initialized', () => { const result = service.renameSymbol('/test/file.ts', 1, 1, 'newName') expect(result).toEqual({ changes: {} }) }) }) describe('getTypeInfo', () => { it('should return undefined when not initialized', () => { const result = service.getTypeInfo('/test/file.ts', 1, 1) expect(result).toBeUndefined() }) }) })

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/currentspace/shortcut_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server