Skip to main content
Glama
detector.spec.ts6.89 kB
import fs from 'fs'; import os from 'os'; import path from 'path'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { detectInstalledIDEs, detectProfile, getPreSelectedProfiles } from './detector.js'; import { IDE_MARKERS } from './profiles-map.js'; // Use real fs operations with temp directories for accurate testing describe('IDE Detection', () => { let tempDir: string; beforeEach(() => { // Create a real temp directory for each test tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tm-detection-test-')); }); afterEach(() => { // Clean up temp directory fs.rmSync(tempDir, { recursive: true, force: true }); }); describe('detectInstalledIDEs', () => { it('should detect a single IDE directory', () => { // Arrange - create .cursor directory fs.mkdirSync(path.join(tempDir, '.cursor')); // Act const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert expect(results).toHaveLength(1); expect(results[0]).toEqual({ profileName: 'cursor', markerPath: '.cursor', displayName: 'Cursor', exists: true }); }); it('should detect multiple IDE directories', () => { // Arrange - create multiple IDE directories fs.mkdirSync(path.join(tempDir, '.cursor')); fs.mkdirSync(path.join(tempDir, '.claude')); fs.mkdirSync(path.join(tempDir, '.windsurf')); // Act const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert expect(results).toHaveLength(3); const profileNames = results.map((r) => r.profileName); expect(profileNames).toContain('cursor'); expect(profileNames).toContain('claude'); expect(profileNames).toContain('windsurf'); }); it('should return empty array when no IDEs are detected', () => { // Act - tempDir has no IDE directories const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert expect(results).toHaveLength(0); }); it('should detect directory markers for all directory-based profiles', () => { // Arrange - create all directory-based markers const directoryMarkers = [ '.cursor', '.claude', '.windsurf', '.roo', '.cline', '.vscode', '.kiro', '.zed', '.kilo', '.trae', '.gemini', '.opencode', '.codex' ]; for (const marker of directoryMarkers) { fs.mkdirSync(path.join(tempDir, marker)); } // Act const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert expect(results.length).toBe(directoryMarkers.length); }); it('should detect file-based markers (GEMINI.md)', () => { // Arrange - create GEMINI.md file (but no .gemini directory) fs.writeFileSync(path.join(tempDir, 'GEMINI.md'), '# Gemini config'); // Act const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert expect(results).toHaveLength(1); expect(results[0]).toEqual({ profileName: 'gemini', markerPath: 'GEMINI.md', displayName: 'Gemini', exists: true }); }); it('should prefer directory marker over file marker for Gemini', () => { // Arrange - create both .gemini directory and GEMINI.md file fs.mkdirSync(path.join(tempDir, '.gemini')); fs.writeFileSync(path.join(tempDir, 'GEMINI.md'), '# Gemini config'); // Act const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert - should only detect once, with directory marker const geminiResults = results.filter((r) => r.profileName === 'gemini'); expect(geminiResults).toHaveLength(1); expect(geminiResults[0].markerPath).toBe('.gemini'); }); it('should not detect files as directories', () => { // Arrange - create a file named .cursor instead of directory fs.writeFileSync(path.join(tempDir, '.cursor'), 'not a directory'); // Act const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert - should not detect because it's a file, not a directory expect(results.filter((r) => r.profileName === 'cursor')).toHaveLength(0); }); it('should not detect directories as files', () => { // Arrange - create a directory named GEMINI.md instead of file fs.mkdirSync(path.join(tempDir, 'GEMINI.md')); // Act const results = detectInstalledIDEs({ projectRoot: tempDir }); // Assert - should not detect GEMINI.md as it's a directory, not a file const geminiResults = results.filter((r) => r.profileName === 'gemini'); expect(geminiResults).toHaveLength(0); }); }); describe('getPreSelectedProfiles', () => { it('should return profile names only', () => { // Arrange fs.mkdirSync(path.join(tempDir, '.cursor')); fs.mkdirSync(path.join(tempDir, '.claude')); // Act const profiles = getPreSelectedProfiles({ projectRoot: tempDir }); // Assert expect(profiles).toEqual(expect.arrayContaining(['cursor', 'claude'])); expect(profiles).toHaveLength(2); }); it('should return empty array when no IDEs detected', () => { // Act const profiles = getPreSelectedProfiles({ projectRoot: tempDir }); // Assert expect(profiles).toEqual([]); }); }); describe('detectProfile', () => { it('should return detection result for existing profile', () => { // Arrange fs.mkdirSync(path.join(tempDir, '.cursor')); // Act const result = detectProfile('cursor', tempDir); // Assert expect(result).toEqual({ profileName: 'cursor', markerPath: '.cursor', displayName: 'Cursor', exists: true }); }); it('should return null for non-existing profile', () => { // Act - no .cursor directory exists const result = detectProfile('cursor', tempDir); // Assert expect(result).toBeNull(); }); it('should return null for non-detectable profile (amp)', () => { // Act const result = detectProfile('amp', tempDir); // Assert expect(result).toBeNull(); }); }); }); describe('IDE_MARKERS', () => { it('should have 13 detectable profiles', () => { // amp is not detectable expect(IDE_MARKERS).toHaveLength(13); }); it('should have valid marker definitions for all profiles', () => { for (const marker of IDE_MARKERS) { expect(marker.profileName).toBeTruthy(); expect(marker.displayName).toBeTruthy(); expect(marker.markers).toBeInstanceOf(Array); expect(marker.markers.length).toBeGreaterThan(0); for (const m of marker.markers) { expect(m.path).toBeTruthy(); expect(['directory', 'file']).toContain(m.type); } } }); it('should include cursor, claude, and windsurf profiles', () => { const profileNames = IDE_MARKERS.map((m) => m.profileName); expect(profileNames).toContain('cursor'); expect(profileNames).toContain('claude'); expect(profileNames).toContain('windsurf'); }); it('should not include amp (no known marker)', () => { const profileNames = IDE_MARKERS.map((m) => m.profileName); expect(profileNames).not.toContain('amp'); }); });

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/eyaltoledano/claude-task-master'

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