UNPKG

ai-test-generator

Version:

AI-powered test generator for JavaScript and TypeScript codebases using Google Gemini

194 lines (193 loc) 8.37 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const client_1 = require("../ai/client"); const prompt_1 = require("../ai/prompt"); const parser_1 = require("../ai/parser"); class Generator { constructor(options) { // Fix the spread operator issue by spreading first, then providing defaults for missing properties this.options = { ...options, testFramework: options.testFramework || 'jest', outputDir: options.outputDir || './tests', testStyle: options.testStyle || 'unit', coverage: options.coverage ?? 80, includeSnapshot: options.includeSnapshot ?? false, timeout: options.timeout || 30000 }; this.aiClient = new client_1.AIClient({ apiKey: this.options.apiKey, model: this.options.aiModel, timeout: this.options.timeout }); } /** * Generate tests for all components that need tests from analysis results * @param analysisResults Results from the code analyzer * @returns Array of generated test files */ async generateTests(analysisResults) { const generatedTests = []; // Default values for rate limiting const requestDelay = this.options.requestDelay || 1000; // 1 second between requests const batchSize = this.options.batchSize || 5; // Process 5 components before pausing const batchDelay = this.options.batchDelay || 5000; // 5 second pause between batches // Collect all components that need tests across all files let allComponentsToTest = []; for (const result of analysisResults) { // Filter components that need tests const componentsToTest = result.components.filter(component => component.needsTest); allComponentsToTest = allComponentsToTest.concat(componentsToTest.map(component => ({ component, filePath: result.filePath }))); } // Process components with rate limiting for (let i = 0; i < allComponentsToTest.length; i++) { const { component, filePath } = allComponentsToTest[i]; try { console.log(`Generating test for component ${i + 1}/${allComponentsToTest.length}: ${component.name}`); const test = await this.generateTestForComponent(component, filePath); if (test) { generatedTests.push(test); } // Apply request delay between API calls if (i < allComponentsToTest.length - 1) { console.log(`Waiting ${requestDelay}ms before next request...`); await this.sleep(requestDelay); } // Apply batch delay after processing batchSize components if ((i + 1) % batchSize === 0 && i < allComponentsToTest.length - 1) { console.log(`Completed batch of ${batchSize}. Pausing for ${batchDelay}ms...`); await this.sleep(batchDelay); } } catch (error) { console.error(`Error generating test for ${component.name}:`, error); // Add extra delay after an error to avoid rate limit issues await this.sleep(requestDelay * 2); } } return generatedTests; } /** * Sleep for a specified duration * @param ms Milliseconds to sleep */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Generate a test for a single component * @param component The component to generate a test for * @param sourceFilePath Path to the source file containing the component * @returns Generated test file */ async generateTestForComponent(component, sourceFilePath) { try { // Create a prompt for the AI const prompt = (0, prompt_1.createPrompt)({ component, sourceFilePath, testFramework: this.options.testFramework, testStyle: this.options.testStyle || 'unit', coverage: this.options.coverage, includeSnapshot: this.options.includeSnapshot }); // Get AI response const aiResponse = await this.aiClient.complete(prompt); // Parse the response to extract just the code const testContent = (0, parser_1.parseResponse)(aiResponse); if (!testContent) { console.warn(`Failed to generate test for ${component.name}: Empty response`); return null; } // Determine output file path const testFilePath = this.getTestFilePath(sourceFilePath, component.name); return { filePath: testFilePath, content: testContent, component }; } catch (error) { console.error(`Error generating test for ${component.name}:`, error); return null; } } /** * Save generated tests to the file system * @param generatedTests Array of generated test data */ async saveTests(generatedTests) { for (const test of generatedTests) { try { // Ensure the directory exists const directory = path.dirname(test.filePath); await fs.promises.mkdir(directory, { recursive: true }); // Write the test file await fs.promises.writeFile(test.filePath, test.content, 'utf8'); console.log(`✅ Test saved: ${test.filePath}`); } catch (error) { console.error(`Error saving test ${test.filePath}:`, error); } } } /** * Get the appropriate file path for the generated test * @param sourceFilePath Original source file path * @param componentName Name of the component being tested * @returns Path for the generated test file */ getTestFilePath(sourceFilePath, componentName) { const parsedPath = path.parse(sourceFilePath); const relativePath = parsedPath.dir.replace(process.cwd(), ''); const outputDir = this.options.outputDir || './tests'; // Create test file name based on source file let testFileName = ''; if (this.options.testFramework === 'jest') { testFileName = `${parsedPath.name}.test${parsedPath.ext}`; } else if (this.options.testFramework === 'mocha') { testFileName = `${parsedPath.name}.spec${parsedPath.ext}`; } else { testFileName = `${parsedPath.name}.test${parsedPath.ext}`; } return path.join(process.cwd(), outputDir, relativePath, testFileName); } } exports.default = Generator;