UNPKG

meld

Version:

Meld: A template language for LLM prompts

198 lines (156 loc) 6.27 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import * as initModule from './init.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import { createInterface } from 'readline'; // Mock fs vi.mock('fs/promises', () => ({ access: vi.fn(), writeFile: vi.fn().mockResolvedValue(undefined) })); // Mock path vi.mock('path', () => ({ join: vi.fn((dir, file) => `${dir}/${file}`), resolve: vi.fn((dir, file) => `${dir}/${file}`), isAbsolute: vi.fn((p) => p.startsWith('/')), normalize: vi.fn((p) => p) })); // Mock readline vi.mock('readline', () => ({ createInterface: vi.fn(() => ({ question: vi.fn(), close: vi.fn() })) })); // Save original process.cwd and process.exit const originalCwd = process.cwd; const originalExit = process.exit; describe('initCommand', () => { let mockReadlineInterface; let mockInitCommand; beforeEach(() => { vi.resetAllMocks(); // Create a mock implementation of initCommand mockInitCommand = vi.fn(async () => { const cwd = process.cwd(); // Check if meld.json already exists try { await fs.access(path.join(cwd, 'meld.json')); console.error('Error: meld.json already exists in this directory.'); process.exit(1); } catch (e) { // File doesn't exist, continue } // Create readline interface const rl = createInterface({ input: process.stdin, output: process.stdout }); // Prompt for project root const projectRoot = await new Promise<string>((resolve) => { rl.question('Project root (must be "." or a subdirectory): ', (answer) => { resolve(answer || '.'); }); }); // Validate the input if (projectRoot !== '.' && (projectRoot.includes('..') || path.isAbsolute(projectRoot) || path.normalize(projectRoot).startsWith('..'))) { console.error('Error: Project root must be "." or a valid subdirectory.'); rl.close(); process.exit(1); } // Create config const config = { projectRoot, version: 1 }; // Write config file await fs.writeFile( path.join(cwd, 'meld.json'), JSON.stringify(config, null, 2) ); console.log(`Meld project initialized successfully.`); console.log(`Project root set to: ${path.resolve(cwd, projectRoot)}`); rl.close(); }); // Replace the original initCommand with our mock vi.spyOn(initModule, 'initCommand').mockImplementation(mockInitCommand); // Mock process.cwd and process.exit process.cwd = vi.fn(() => '/test/project'); process.exit = vi.fn((code) => { throw new Error(`Process exited with code ${code}`); }); // Create a mock readline interface with a working question method mockReadlineInterface = { question: vi.fn(), close: vi.fn() }; // Set up the mock to return our interface vi.mocked(createInterface).mockReturnValue(mockReadlineInterface); // Ensure path.join returns the expected path vi.mocked(path.join).mockImplementation((dir, file) => `${dir}/${file}`); }); afterEach(() => { // Restore original process methods process.cwd = originalCwd; process.exit = originalExit; vi.resetAllMocks(); }); it('should create a meld.json file with default project root', async () => { // Mock file doesn't exist vi.mocked(fs.access).mockRejectedValue(new Error('File not found')); // Set up the question mock to call the callback with '.' mockReadlineInterface.question.mockImplementation((_, callback) => { callback('.'); }); await initModule.initCommand(); // Verify meld.json was created with correct content expect(fs.writeFile).toHaveBeenCalledWith( '/test/project/meld.json', JSON.stringify({ projectRoot: '.', version: 1 }, null, 2) ); }); it('should create a meld.json file with custom project root', async () => { // Mock file doesn't exist vi.mocked(fs.access).mockRejectedValue(new Error('File not found')); // Set up the question mock to call the callback with 'src' mockReadlineInterface.question.mockImplementation((_, callback) => { callback('src'); }); // Ensure path.normalize returns the expected path for 'src' vi.mocked(path.normalize).mockReturnValue('src'); await initModule.initCommand(); // Verify meld.json was created with correct content expect(fs.writeFile).toHaveBeenCalledWith( '/test/project/meld.json', JSON.stringify({ projectRoot: 'src', version: 1 }, null, 2) ); }); it('should reject invalid project root paths', async () => { // Mock file doesn't exist vi.mocked(fs.access).mockRejectedValue(new Error('File not found')); // Set up the question mock to call the callback with an invalid path mockReadlineInterface.question.mockImplementation((_, callback) => { callback('../outside'); }); // Mock path.normalize to return '../outside' for the invalid path check vi.mocked(path.normalize).mockReturnValue('../outside'); // Expect process.exit to be called await expect(initModule.initCommand()).rejects.toThrow('Process exited with code 1'); // Verify meld.json was not created expect(fs.writeFile).not.toHaveBeenCalled(); }); it.skip('should exit if meld.json already exists', async () => { // Mock that the file exists vi.mocked(fs.access).mockResolvedValue(undefined); // Create a spy for console.error const consoleErrorSpy = vi.spyOn(console, 'error'); // Expect process.exit to be called await expect(initModule.initCommand()).rejects.toThrow('Process exited with code 1'); // Verify console.error was called with the expected message expect(consoleErrorSpy).toHaveBeenCalledWith('Error: meld.json already exists in this directory.'); // Verify readline was not used expect(createInterface).not.toHaveBeenCalled(); // Verify meld.json was not created expect(fs.writeFile).not.toHaveBeenCalled(); }); });