/**
* Tests for Platform Registry
*
* @module @agentkits/memory/cli/__tests__/platforms.test
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import {
PLATFORMS,
ALL_PLATFORM_IDS,
detectPlatforms,
resolvePlatforms,
type PlatformId,
} from '../platforms.js';
describe('Platform Registry', () => {
// ===== PLATFORMS constant =====
describe('PLATFORMS', () => {
it('should define all 5 platforms', () => {
expect(ALL_PLATFORM_IDS).toHaveLength(5);
expect(ALL_PLATFORM_IDS).toContain('claude-code');
expect(ALL_PLATFORM_IDS).toContain('cursor');
expect(ALL_PLATFORM_IDS).toContain('windsurf');
expect(ALL_PLATFORM_IDS).toContain('cline');
expect(ALL_PLATFORM_IDS).toContain('opencode');
});
it('should have valid configDir for each platform', () => {
for (const platform of Object.values(PLATFORMS)) {
expect(platform.configDir).toBeTruthy();
expect(platform.configDir.startsWith('.')).toBe(true);
}
});
it('should have valid mcpConfigPath for each platform', () => {
for (const platform of Object.values(PLATFORMS)) {
expect(platform.mcpConfigPath).toBeTruthy();
expect(platform.mcpConfigPath).toMatch(/\.(json)$/);
}
});
it('should mark claude-code and opencode as supporting hooks', () => {
expect(PLATFORMS['claude-code'].supportsHooks).toBe(true);
expect(PLATFORMS.opencode.supportsHooks).toBe(true);
});
it('should mark cursor, windsurf, cline as not supporting hooks', () => {
expect(PLATFORMS.cursor.supportsHooks).toBe(false);
expect(PLATFORMS.windsurf.supportsHooks).toBe(false);
expect(PLATFORMS.cline.supportsHooks).toBe(false);
});
it('should have rules files for cursor, windsurf, cline', () => {
expect(PLATFORMS.cursor.rulesFile).toBe('.cursorrules');
expect(PLATFORMS.windsurf.rulesFile).toBe('.windsurfrules');
expect(PLATFORMS.cline.rulesFile).toBe('.clinerules');
});
it('should not have rules files for claude-code and opencode', () => {
expect(PLATFORMS['claude-code'].rulesFile).toBeNull();
expect(PLATFORMS.opencode.rulesFile).toBeNull();
});
it('should only have skillsDir for claude-code', () => {
expect(PLATFORMS['claude-code'].skillsDir).toBe('.claude/skills');
expect(PLATFORMS.cursor.skillsDir).toBeNull();
expect(PLATFORMS.windsurf.skillsDir).toBeNull();
expect(PLATFORMS.cline.skillsDir).toBeNull();
expect(PLATFORMS.opencode.skillsDir).toBeNull();
});
it('should use embedded mcpConfigFormat only for claude-code', () => {
expect(PLATFORMS['claude-code'].mcpConfigFormat).toBe('embedded');
expect(PLATFORMS.cursor.mcpConfigFormat).toBe('standalone');
expect(PLATFORMS.windsurf.mcpConfigFormat).toBe('standalone');
expect(PLATFORMS.cline.mcpConfigFormat).toBe('standalone');
expect(PLATFORMS.opencode.mcpConfigFormat).toBe('standalone');
});
});
// ===== detectPlatforms =====
describe('detectPlatforms', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'platforms-test-'));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('should return empty array for empty directory', () => {
expect(detectPlatforms(tmpDir)).toEqual([]);
});
it('should detect claude-code from .claude directory', () => {
fs.mkdirSync(path.join(tmpDir, '.claude'));
const detected = detectPlatforms(tmpDir);
expect(detected).toContain('claude-code');
});
it('should detect cursor from .cursor directory', () => {
fs.mkdirSync(path.join(tmpDir, '.cursor'));
const detected = detectPlatforms(tmpDir);
expect(detected).toContain('cursor');
});
it('should detect multiple platforms', () => {
fs.mkdirSync(path.join(tmpDir, '.claude'));
fs.mkdirSync(path.join(tmpDir, '.cursor'));
fs.mkdirSync(path.join(tmpDir, '.windsurf'));
const detected = detectPlatforms(tmpDir);
expect(detected).toContain('claude-code');
expect(detected).toContain('cursor');
expect(detected).toContain('windsurf');
expect(detected).toHaveLength(3);
});
it('should detect all 5 platforms when all present', () => {
for (const platform of Object.values(PLATFORMS)) {
fs.mkdirSync(path.join(tmpDir, platform.configDir));
}
const detected = detectPlatforms(tmpDir);
expect(detected).toHaveLength(5);
});
});
// ===== resolvePlatforms =====
describe('resolvePlatforms', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'platforms-test-'));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('should return all platforms for "all"', () => {
const result = resolvePlatforms('all', tmpDir);
expect(result).toEqual(ALL_PLATFORM_IDS);
});
it('should return single platform for "cursor"', () => {
const result = resolvePlatforms('cursor', tmpDir);
expect(result).toEqual(['cursor']);
});
it('should return multiple platforms for comma-separated list', () => {
const result = resolvePlatforms('cursor,windsurf', tmpDir);
expect(result).toEqual(['cursor', 'windsurf']);
});
it('should filter invalid platform names', () => {
const result = resolvePlatforms('cursor,invalid,windsurf', tmpDir);
expect(result).toEqual(['cursor', 'windsurf']);
});
it('should fall back to auto-detect when all names are invalid', () => {
fs.mkdirSync(path.join(tmpDir, '.cursor'));
const result = resolvePlatforms('invalid', tmpDir);
expect(result).toContain('cursor');
});
it('should auto-detect when no platformArg is given', () => {
fs.mkdirSync(path.join(tmpDir, '.cursor'));
fs.mkdirSync(path.join(tmpDir, '.claude'));
const result = resolvePlatforms(undefined, tmpDir);
expect(result).toContain('claude-code');
expect(result).toContain('cursor');
});
it('should default to claude-code when nothing detected', () => {
const result = resolvePlatforms(undefined, tmpDir);
expect(result).toEqual(['claude-code']);
});
it('should handle empty string by auto-detecting', () => {
const result = resolvePlatforms('', tmpDir);
// Empty string is falsy so falls through to auto-detect
expect(result).toEqual(['claude-code']);
});
});
});