import { describe, it, expect, beforeEach, vi } from 'vitest'; import { CommandLoader } from '../../../src/commands/loader.js'; // Mock fs/promises vi.mock('fs/promises', () => ({ access: vi.fn(), readdir: vi.fn(), readFile: vi.fn(), })); import * as fs from 'fs/promises'; describe('CommandLoader - Command 加载器', () => { let loader: CommandLoader; beforeEach(() => { vi.clearAllMocks(); loader = new CommandLoader(); }); describe('parseMarkdownCommand - 解析 Markdown', () => { it('解析带 frontmatter 的 Markdown', () => { const content = `--- description: 代码审查 agent: explore model: sonnet subtask: true --- You are a code reviewer. Input: $ARGUMENTS `; const command = loader.parseMarkdownCommand(content, 'review', 'user'); expect(command).not.toBeNull(); expect(command?.name).toBe('review'); expect(command?.description).toBe('代码审查'); expect(command?.agent).toBe('explore'); expect(command?.model).toBe('sonnet'); expect(command?.subtask).toBe(true); expect(command?.template).toContain('You are a code reviewer'); expect(command?.template).toContain('$ARGUMENTS'); expect(command?.source).toBe('user'); }); it('解析不带 frontmatter 的 Markdown', () => { const content = `# Simple Command This is a simple prompt template. $ARGUMENTS `; const command = loader.parseMarkdownCommand(content, 'simple', 'project'); expect(command).not.toBeNull(); expect(command?.name).toBe('simple'); expect(command?.description).toBeUndefined(); expect(command?.template).toContain('Simple Command'); expect(command?.template).toContain('$ARGUMENTS'); expect(command?.source).toBe('project'); }); it('空模板返回 null', () => { const content = `--- description: Empty --- `; const command = loader.parseMarkdownCommand(content, 'empty', 'user'); expect(command).toBeNull(); }); }); describe('loadFromFile - 从文件加载', () => { it('从 Markdown 文件加载 Command', async () => { const mdContent = `--- description: Test command --- Test template $ARGUMENTS `; vi.mocked(fs.readFile).mockResolvedValue(mdContent); const command = await loader.loadFromFile( '/base/commands/test.md', '/base/commands', 'user' ); expect(command).not.toBeNull(); expect(command?.name).toBe('test'); expect(command?.description).toBe('Test command'); }); it('处理嵌套目录路径', async () => { const mdContent = `--- description: Deploy staging --- Deploy to staging `; vi.mocked(fs.readFile).mockResolvedValue(mdContent); const command = await loader.loadFromFile( '/base/commands/deploy/staging.md', '/base/commands', 'project' ); expect(command).not.toBeNull(); expect(command?.name).toBe('deploy/staging'); }); }); describe('loadFromDirectory - 从目录加载', () => { it('目录不存在时返回空数组', async () => { vi.mocked(fs.access).mockRejectedValue(new Error('Not found')); const commands = await loader.loadFromDirectory('/non-existent', 'user'); expect(commands).toEqual([]); }); it('加载目录中的所有 Markdown 文件', async () => { vi.mocked(fs.access).mockResolvedValue(undefined); vi.mocked(fs.readdir).mockResolvedValue([ { name: 'cmd1.md', isFile: () => true, isDirectory: () => false }, { name: 'cmd2.md', isFile: () => true, isDirectory: () => false }, { name: 'readme.txt', isFile: () => true, isDirectory: () => false }, // 非 .md ] as any); vi.mocked(fs.readFile) .mockResolvedValueOnce('---\ndescription: Cmd1\n---\nTemplate 1') .mockResolvedValueOnce('---\ndescription: Cmd2\n---\nTemplate 2'); const commands = await loader.loadFromDirectory('/test', 'user'); expect(commands.length).toBe(2); expect(commands.map((c) => c.name)).toContain('cmd1'); expect(commands.map((c) => c.name)).toContain('cmd2'); }); it('递归加载子目录', async () => { vi.mocked(fs.access).mockResolvedValue(undefined); vi.mocked(fs.readdir) .mockResolvedValueOnce([ { name: 'root.md', isFile: () => true, isDirectory: () => false }, { name: 'subdir', isFile: () => false, isDirectory: () => true }, ] as any) .mockResolvedValueOnce([ { name: 'sub.md', isFile: () => true, isDirectory: () => false }, ] as any); vi.mocked(fs.readFile) .mockResolvedValueOnce('Root template') .mockResolvedValueOnce('Sub template'); const commands = await loader.loadFromDirectory('/test', 'project'); expect(commands.length).toBe(2); expect(commands.map((c) => c.name)).toContain('root'); expect(commands.map((c) => c.name)).toContain('subdir/sub'); }); it('跳过隐藏目录', async () => { vi.mocked(fs.access).mockResolvedValue(undefined); vi.mocked(fs.readdir).mockResolvedValue([ { name: '.hidden', isFile: () => false, isDirectory: () => true }, { name: 'visible.md', isFile: () => true, isDirectory: () => false }, ] as any); vi.mocked(fs.readFile).mockResolvedValue('Template'); const commands = await loader.loadFromDirectory('/test', 'user'); expect(commands.length).toBe(1); expect(commands[0].name).toBe('visible'); }); }); describe('目录路径', () => { it('getUserCommandsDir 返回用户 Commands 目录', () => { const originalHome = process.env.HOME; process.env.HOME = '/home/testuser'; const dir = loader.getUserCommandsDir(); expect(dir).toBe('/home/testuser/.config/ai-terminal/commands'); process.env.HOME = originalHome; }); it('getProjectCommandsDir 返回项目 Commands 目录', () => { const dir = loader.getProjectCommandsDir('/workspace'); expect(dir).toBe('/workspace/.ai-terminal/commands'); }); }); });