languageServer.test.ts•6.78 kB
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { LanguageServerService } from '../../src/languageServerService'
import * as fs from 'fs/promises'
import * as path from 'path'
import { mkdtemp, rm } from 'fs/promises'
import { tmpdir } from 'os'
describe('LanguageServer Integration Tests', () => {
let service: LanguageServerService
let testDir: string
beforeAll(async () => {
// Create a temporary test directory
testDir = await mkdtemp(path.join(tmpdir(), 'langserver-test-'))
service = new LanguageServerService()
// Create a simple TypeScript project structure
await fs.mkdir(path.join(testDir, 'src'), { recursive: true })
// Create tsconfig.json
await fs.writeFile(
path.join(testDir, 'tsconfig.json'),
JSON.stringify(
{
compilerOptions: {
target: 'es2020',
module: 'commonjs',
strict: true,
esModuleInterop: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
},
include: ['src/**/*'],
},
null,
2
)
)
// Create package.json
await fs.writeFile(
path.join(testDir, 'package.json'),
JSON.stringify(
{
name: 'test-project',
version: '1.0.0',
type: 'commonjs',
},
null,
2
)
)
// Create a test TypeScript file
await fs.writeFile(
path.join(testDir, 'src', 'test.ts'),
`interface User {
id: number;
name: string;
email: string;
}
class UserService {
private users: User[] = [];
addUser(user: User): void {
this.users.push(user);
}
getUser(id: number): User | undefined {
return this.users.find(u => u.id === id);
}
getAllUsers(): User[] {
return this.users;
}
}
const service = new UserService();
const testUser: User = {
id: 1,
name: "Test User",
email: "test@example.com"
};
service.addUser(testUser);
`
)
// Create another file for testing references
await fs.writeFile(
path.join(testDir, 'src', 'consumer.ts'),
`import { UserService } from './test';
const myService = new UserService();
myService.addUser({
id: 2,
name: "Another User",
email: "another@example.com"
});
`
)
})
afterAll(async () => {
// Clean up test directory
await rm(testDir, { recursive: true, force: true })
})
describe('TypeScript Language Server Operations', () => {
it('should initialize and discover the test project', async () => {
const result = await service.initialize(testDir, true)
expect(result.projects).toHaveLength(1)
expect(result.projects[0]?.name).toBe(path.basename(testDir))
expect(result.projects[0]?.type).toBe('node')
})
it('should provide diagnostics for TypeScript errors', async () => {
// Add a file with errors
const errorFile = path.join(testDir, 'src', 'errors.ts')
await fs.writeFile(
errorFile,
`const x: string = 123; // Type error
const y = undefined.property; // Runtime error
`
)
await service.initialize(testDir, true)
const diagnostics = service.getDiagnostics({ file: errorFile })
expect(diagnostics).toHaveLength(1)
expect(diagnostics[0]?.diagnostics.length).toBeGreaterThan(0)
const errors = diagnostics[0]?.diagnostics.filter((d) => d.severity === 'error') || []
expect(errors.length).toBeGreaterThan(0)
})
it('should handle operations on non-initialized service gracefully', () => {
const newService = new LanguageServerService()
// All operations should return empty/undefined when not initialized
expect(newService.getDiagnostics({})).toEqual([])
expect(newService.getCompletions('/test/file.ts', 1, 1)).toEqual({ completions: [] })
expect(newService.getHover('/test/file.ts', 1, 1)).toBeUndefined()
expect(newService.getDefinition('/test/file.ts', 1, 1)).toBeUndefined()
expect(newService.findReferences('/test/file.ts', 1, 1)).toEqual({ references: [] })
expect(newService.renameSymbol('/test/file.ts', 1, 1, 'newName')).toEqual({
changes: {},
})
expect(newService.getTypeInfo('/test/file.ts', 1, 1)).toBeUndefined()
})
it('should provide completions after initialization', async () => {
await service.initialize(testDir, true)
const testFile = path.join(testDir, 'src', 'test.ts')
// We just check that we get some completions back
const completions = service.getCompletions(testFile, 30, 1)
expect(completions.completions.length).toBeGreaterThan(0)
})
it('should find and rename symbols', async () => {
await service.initialize(testDir, true)
const testFile = path.join(testDir, 'src', 'test.ts')
// Try to rename something - the actual behavior depends on TypeScript
const renameResult = service.renameSymbol(testFile, 8, 13, 'userList')
// We expect some result, even if it's empty
expect(renameResult).toBeDefined()
expect(renameResult.changes).toBeDefined()
})
it('should handle multi-project scenarios', async () => {
// Create a second project
const project2Dir = path.join(testDir, 'project2')
await fs.mkdir(path.join(project2Dir, 'src'), { recursive: true })
await fs.writeFile(
path.join(project2Dir, 'tsconfig.json'),
JSON.stringify({
compilerOptions: {
target: 'es2020',
module: 'esnext',
},
})
)
await fs.writeFile(
path.join(project2Dir, 'package.json'),
JSON.stringify({
name: 'project2',
type: 'module',
})
)
const result = await service.initialize(testDir, true)
// Should discover both projects
expect(result.projects.length).toBeGreaterThanOrEqual(2)
})
})
})