ai-test-generator
Version:
AI-powered test generator for JavaScript and TypeScript codebases using Google Gemini
211 lines (210 loc) • 8.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AIClient = exports.Generator = exports.Analyzer = exports.AITestGenerator = void 0;
const analyzer_1 = __importDefault(require("./core/analyzer"));
exports.Analyzer = analyzer_1.default;
const generator_1 = __importDefault(require("./core/generator"));
exports.Generator = generator_1.default;
const client_1 = require("./ai/client");
Object.defineProperty(exports, "AIClient", { enumerable: true, get: function () { return client_1.AIClient; } });
class AITestGenerator {
constructor(options = {
testFramework: 'jest'
}) {
this.options = {
...options,
testFramework: options.testFramework || 'jest',
outputDir: options.outputDir || './tests',
testStyle: options.testStyle || 'unit',
dryRun: options.dryRun ?? false,
verbose: options.verbose ?? false,
interactive: options.interactive ?? true
};
this.analyzer = new analyzer_1.default();
this.generator = new generator_1.default(this.options);
}
/**
* Generate tests for an entire codebase
* @param codebasePath Path to the root directory of the codebase
* @returns This instance, for chaining
*/
async generateForCodebase(codebasePath = this.options.codebasePath || process.cwd()) {
try {
this.log(`Analyzing codebase at: ${codebasePath}`);
// Analyze the codebase
await this.analyzer.analyzeCodebase(codebasePath, {
excludePaths: this.options.excludePaths,
includePatterns: this.options.includePatterns,
testThreshold: this.options.testThreshold
});
const results = this.analyzer.getAnalysisResults();
this.log(`Found ${this.countComponentsNeedingTests(results)} components that need tests`);
// Generate and save tests
return this.generateFromAnalysis(results);
}
catch (error) {
console.error('Error generating tests for codebase:', error);
return this;
}
}
/**
* Generate tests for a single file
* @param filePath Path to the file to generate tests for
* @returns This instance, for chaining
*/
async generateForFile(filePath) {
try {
this.log(`Analyzing file: ${filePath}`);
// Analyze the file
await this.analyzer.analyzeFile(filePath);
const results = this.analyzer.getAnalysisResults();
this.log(`Found ${this.countComponentsNeedingTests(results)} components that need tests`);
// Generate and save tests
return this.generateFromAnalysis(results);
}
catch (error) {
console.error('Error generating tests for file:', error);
return this;
}
}
/**
* Generate tests from analysis results
* @param results Analysis results from the analyzer
* @returns This instance, for chaining
*/
async generateFromAnalysis(results) {
// Generate tests for components that need them
const generatedTests = await this.generator.generateTests(results);
this.log(`Generated ${generatedTests.length} test files`);
if (!this.options.dryRun && generatedTests.length > 0) {
await this.generator.saveTests(generatedTests);
}
else if (this.options.dryRun) {
this.log('Dry run - not saving tests');
this.logGeneratedTests(generatedTests);
}
return this;
}
/**
* Count components that need tests in analysis results
* @param results Analysis results
* @returns Count of components needing tests
*/
countComponentsNeedingTests(results) {
return results.reduce((count, result) => {
return count + result.components.filter(c => c.needsTest).length;
}, 0);
}
/**
* Log information if verbose mode is enabled
* @param message Message to log
*/
log(message) {
if (this.options.verbose) {
console.log(`[AI-Test-Generator] ${message}`);
}
}
/**
* Log information about generated tests in dry run mode
* @param tests Generated test files
*/
logGeneratedTests(tests) {
if (this.options.dryRun && this.options.verbose) {
console.log('\n--- Generated Test Files (Dry Run) ---');
tests.forEach(test => {
console.log(`\nTest for ${test.component.name} (${test.component.type})`);
console.log(`File: ${test.filePath}`);
console.log('Preview:');
console.log(test.content.split('\n').slice(0, 5).join('\n') + '...');
});
}
}
}
exports.AITestGenerator = AITestGenerator;
// Export default instance and main classes
exports.default = AITestGenerator;
// CLI support for direct usage via npx/npm run
if (require.main === module) {
const args = process.argv.slice(2);
const options = {
verbose: true,
testFramework: 'jest' // Add this required property
};
// Simple CLI argument parsing
let codebasePath = process.cwd();
for (let i = 0; i < args.length; i++) {
if (args[i] === '--path' && i + 1 < args.length) {
codebasePath = args[++i];
}
else if (args[i] === '--framework' && i + 1 < args.length) {
options.testFramework = args[++i];
}
else if (args[i] === '--output' && i + 1 < args.length) {
options.outputDir = args[++i];
}
else if (args[i] === '--dry-run') {
options.dryRun = true;
}
else if (args[i] === '--file' && i + 1 < args.length) {
options.singleFile = args[++i];
}
else if (args[i] === '--api-key' && i + 1 < args.length) {
options.apiKey = args[++i];
}
else if (args[i] === '--model' && i + 1 < args.length) {
options.aiModel = args[++i];
}
else if (args[i] === '--gemini') {
// Default to Gemini model if --gemini flag is used
options.aiModel = 'gemini-1.5-flash';
}
else if (args[i] === '--non-interactive') {
options.interactive = false;
}
else if (args[i] === '--request-delay' && i + 1 < args.length) {
options.requestDelay = parseInt(args[++i], 10);
}
else if (args[i] === '--batch-size' && i + 1 < args.length) {
options.batchSize = parseInt(args[++i], 10);
}
else if (args[i] === '--batch-delay' && i + 1 < args.length) {
options.batchDelay = parseInt(args[++i], 10);
}
else if (args[i] === '--quota-friendly') {
// Preset for quota-friendly operation (large delays between requests)
options.requestDelay = 5000; // 5 seconds between requests
options.batchSize = 3; // Only 3 requests per batch
options.batchDelay = 30000; // 30 seconds between batches
}
else if (args[i] === '--ultra-quota-friendly') {
options.strictQuotaMode = true;
options.requestDelay = 45000; // 45 seconds between requests
options.aiModel = 'gemini-1.5-flash'; // Use the smaller model
console.log('📢 Ultra-quota-friendly mode enabled. This will be very slow but should avoid quota errors.');
}
else if (args[i] === '--offline') {
options.offlineMode = true;
console.log('📴 Offline mode enabled. Simple test templates will be generated without API calls.');
}
}
// Run the test generator
const generator = new AITestGenerator(options);
(async () => {
try {
if (options.singleFile) {
await generator.generateForFile(options.singleFile);
}
else {
await generator.generateForCodebase(codebasePath);
}
}
catch (error) {
console.error('Error running AI Test Generator:', error);
process.exit(1);
}
})();
}