UNPKG

filetree-pro

Version:

A powerful file tree generator for VS Code and Cursor. Generate beautiful file trees in multiple formats with smart exclusions and custom configurations.

299 lines (247 loc) 10.4 kB
/** * Test Suite: GenerateTreeCommand * Tests the main tree generation command with proper method signatures * * @author FileTree Pro Team * @since 0.3.0 */ import * as vscode from 'vscode'; import { GenerateTreeCommand } from '../commands/generateTreeCommand'; import { ExclusionService } from '../services/exclusionService'; import { TreeBuilderService } from '../services/treeBuilderService'; describe('GenerateTreeCommand', () => { let command: GenerateTreeCommand; let mockTreeBuilderService: jest.Mocked<TreeBuilderService>; let mockExclusionService: jest.Mocked<ExclusionService>; beforeEach(() => { // Create mock ExclusionService mockExclusionService = { shouldExclude: jest.fn().mockReturnValue(false), readGitignore: jest.fn().mockResolvedValue(['node_modules/', 'dist/']), dispose: jest.fn(), } as any; // Create mock TreeBuilderService mockTreeBuilderService = { buildFileTreeItems: jest.fn().mockResolvedValue([ { name: 'src', type: 'folder', path: '/test/src', children: [ { name: 'index.ts', type: 'file', path: '/test/src/index.ts' }, { name: 'utils.ts', type: 'file', path: '/test/src/utils.ts' }, ], }, { name: 'package.json', type: 'file', path: '/test/package.json' }, { name: 'README.md', type: 'file', path: '/test/README.md' }, ]), generateTreeLines: jest.fn().mockResolvedValue(undefined), } as any; // Create command instance command = new GenerateTreeCommand(mockTreeBuilderService); // Reset VS Code mocks jest.clearAllMocks(); }); // Helper function to setup successful execution mocks const setupSuccessfulMocks = (format = 'markdown', icons = true) => { (vscode.window.showQuickPick as jest.Mock) .mockResolvedValueOnce({ label: `📄 ${format}`, value: format }) .mockResolvedValueOnce(icons ? 'With Icons' : 'Without Icons'); (vscode.window.withProgress as jest.Mock).mockImplementation(async (options, task) => { const progress = { report: jest.fn() }; const token = { isCancellationRequested: false, onCancellationRequested: jest.fn(), }; return task(progress, token); }); (vscode.workspace.openTextDocument as jest.Mock).mockResolvedValue({ uri: vscode.Uri.file('/test/output.md'), languageId: format === 'json' ? 'json' : format === 'svg' ? 'xml' : format === 'ascii' ? 'plaintext' : 'markdown', getText: jest.fn().mockReturnValue('# Mock content'), }); (vscode.window.showTextDocument as jest.Mock).mockResolvedValue({}); }; describe('Command Execution', () => { test('should show error when URI is not provided', async () => { await command.execute(undefined as any); expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( 'Please right-click on a folder to generate file tree' ); expect(mockTreeBuilderService.buildFileTreeItems).not.toHaveBeenCalled(); }); test('should execute successfully with valid URI', async () => { setupSuccessfulMocks(); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.window.showQuickPick).toHaveBeenCalledTimes(2); expect(mockTreeBuilderService.buildFileTreeItems).toHaveBeenCalled(); expect(vscode.workspace.openTextDocument).toHaveBeenCalled(); expect(vscode.window.showTextDocument).toHaveBeenCalled(); expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( expect.stringContaining('File tree generated successfully') ); }); test('should handle user cancelling format selection', async () => { (vscode.window.showQuickPick as jest.Mock).mockResolvedValueOnce(undefined); // User cancels const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(mockTreeBuilderService.buildFileTreeItems).not.toHaveBeenCalled(); expect(vscode.window.showInformationMessage).not.toHaveBeenCalled(); }); test('should handle user cancelling icon selection', async () => { (vscode.window.showQuickPick as jest.Mock) .mockResolvedValueOnce({ label: '📄 Markdown', value: 'markdown' }) .mockResolvedValueOnce(undefined); // User cancels icon choice const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(mockTreeBuilderService.buildFileTreeItems).not.toHaveBeenCalled(); }); test('should handle errors during tree generation', async () => { setupSuccessfulMocks(); mockTreeBuilderService.buildFileTreeItems.mockRejectedValueOnce( new Error('Permission denied') ); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( expect.stringContaining('Failed to generate file tree') ); }); }); describe('Format Selection', () => { test('should support Markdown format', async () => { setupSuccessfulMocks('markdown'); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.workspace.openTextDocument).toHaveBeenCalledWith( expect.objectContaining({ language: 'markdown' }) ); }); test('should support JSON format', async () => { setupSuccessfulMocks('json'); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.workspace.openTextDocument).toHaveBeenCalledWith( expect.objectContaining({ language: 'json' }) ); }); test('should support SVG format', async () => { setupSuccessfulMocks('svg'); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.workspace.openTextDocument).toHaveBeenCalledWith( expect.objectContaining({ language: 'xml' }) ); }); test('should support ASCII format', async () => { setupSuccessfulMocks('ascii'); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.workspace.openTextDocument).toHaveBeenCalledWith( expect.objectContaining({ language: 'plaintext' }) ); }); }); describe('Icon Preferences', () => { test('should respect "With Icons" preference', async () => { setupSuccessfulMocks('markdown', true); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); // Verify buildFileTreeItems was called with progress callback expect(mockTreeBuilderService.buildFileTreeItems).toHaveBeenCalledWith( expect.any(String), // rootPath 10, // maxDepth expect.any(String), // rootPath again 0, // depth expect.any(Function) // progressCallback ); }); test('should respect "Without Icons" preference', async () => { setupSuccessfulMocks('markdown', false); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(mockTreeBuilderService.buildFileTreeItems).toHaveBeenCalled(); }); }); describe('Progress Reporting', () => { test('should show progress notification during generation', async () => { setupSuccessfulMocks(); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.window.withProgress).toHaveBeenCalledWith( expect.objectContaining({ location: vscode.ProgressLocation.Notification, cancellable: true, }), expect.any(Function) ); }); test('should handle cancellation during progress', async () => { (vscode.window.showQuickPick as jest.Mock) .mockResolvedValueOnce({ label: '📄 Markdown', value: 'markdown' }) .mockResolvedValueOnce('With Icons'); (vscode.window.withProgress as jest.Mock).mockImplementation(async (options, task) => { const progress = { report: jest.fn() }; const token = { isCancellationRequested: true, // Simulate cancellation onCancellationRequested: jest.fn(), }; return task(progress, token); }); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); expect(vscode.window.showInformationMessage).toHaveBeenCalledWith( 'Tree generation cancelled' ); }); test('should report progress increments', async () => { let reportedProgress: any[] = []; (vscode.window.showQuickPick as jest.Mock) .mockResolvedValueOnce({ label: '📄 Markdown', value: 'markdown' }) .mockResolvedValueOnce('With Icons'); (vscode.window.withProgress as jest.Mock).mockImplementation(async (options, task) => { const progress = { report: jest.fn(value => reportedProgress.push(value)), }; const token = { isCancellationRequested: false, onCancellationRequested: jest.fn(), }; return task(progress, token); }); (vscode.workspace.openTextDocument as jest.Mock).mockResolvedValue({ uri: vscode.Uri.file('/test/output.md'), languageId: 'markdown', getText: jest.fn().mockReturnValue('# Mock content'), }); (vscode.window.showTextDocument as jest.Mock).mockResolvedValue({}); const mockUri = vscode.Uri.file('/test/project'); await command.execute(mockUri); // Should have reported progress at various stages expect(reportedProgress.length).toBeGreaterThan(0); expect(reportedProgress.some(p => p.message?.includes('Starting'))).toBe(true); }); }); describe('Command Registration', () => { test('should register command with VS Code', () => { const mockContext = { subscriptions: [], extensionPath: '/test/extension', } as any; const disposable = GenerateTreeCommand.register(mockContext, mockTreeBuilderService); expect(vscode.commands.registerCommand).toHaveBeenCalledWith( 'filetree-pro.generateFileTree', expect.any(Function) ); expect(disposable).toBeDefined(); }); }); });