agentsqripts
Version:
Comprehensive static code analysis toolkit for identifying technical debt, security vulnerabilities, performance issues, and code quality problems
270 lines (212 loc) • 9.51 kB
JavaScript
/**
* @file Unit tests for project refactoring analyzer
* @description Tests project-level export promotion analysis and refactoring recommendations
*/
const { analyzeProjectRefactoring } = require('./projectRefactoringAnalyzer');
const qtests = require('qtests');
const fs = require('fs');
const path = require('path');
/**
* Test runner for project refactoring analyzer
*/
async function runTests() {
console.log('=== Testing Project Refactoring Analyzer ===');
const results = {
total: 0,
passed: 0
};
// Test analyzeProjectRefactoring with sample project structure
results.total++;
try {
// Create temporary test directory structure
const tempDir = path.join(__dirname, 'temp-refactor-test');
const utilsDir = path.join(tempDir, 'utils');
const componentsDir = path.join(tempDir, 'components');
// Create directories
fs.mkdirSync(tempDir, { recursive: true });
fs.mkdirSync(utilsDir, { recursive: true });
fs.mkdirSync(componentsDir, { recursive: true });
// Create test files with unexported utilities
const utilFile1 = path.join(utilsDir, 'helpers.js');
const utilContent1 = `
// Utility functions that could be exported
function formatDate(date) {
return date.toISOString().split('T')[0];
}
function validateEmail(email) {
return email.includes('@') && email.includes('.');
}
// This one is already exported
export function exportedUtil() {
return 'already exported';
}
`;
const utilFile2 = path.join(utilsDir, 'calculations.js');
const utilContent2 = `
function calculatePercentage(part, whole) {
return (part / whole) * 100;
}
function roundToDecimal(num, places) {
return Math.round(num * Math.pow(10, places)) / Math.pow(10, places);
}
`;
const componentFile = path.join(componentsDir, 'Button.js');
const componentContent = `
import React from 'react';
// This is a component, not a utility
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
export default Button;
`;
fs.writeFileSync(utilFile1, utilContent1);
fs.writeFileSync(utilFile2, utilContent2);
fs.writeFileSync(componentFile, componentContent);
// Analyze the project
const results = await analyzeProjectRefactoring(tempDir, {
extensions: ['.js'],
includeNonUtility: false
});
qtests.assert(typeof results === 'object', 'analyzeProjectRefactoring should return object');
qtests.assert(typeof results.summary === 'object', 'Results should include summary');
qtests.assert(Array.isArray(results.opportunities), 'Results should include opportunities array');
qtests.assert(Array.isArray(results.recommendations), 'Results should include recommendations array');
// Should find unexported utility functions
qtests.assert(results.summary.totalFiles >= 2, 'Should analyze utility files');
qtests.assert(results.summary.totalOpportunities >= 4, 'Should find unexported utility functions');
// Clean up
fs.rmSync(tempDir, { recursive: true, force: true });
console.log('✓ analyzeProjectRefactoring correctly analyzes project structure');
results.passed++;
} catch (error) {
console.log(`✗ analyzeProjectRefactoring test failed: ${error.message}`);
}
// Test with target directories option
results.total++;
try {
const tempDir = path.join(__dirname, 'temp-target-test');
const libDir = path.join(tempDir, 'lib');
const srcDir = path.join(tempDir, 'src');
fs.mkdirSync(tempDir, { recursive: true });
fs.mkdirSync(libDir, { recursive: true });
fs.mkdirSync(srcDir, { recursive: true });
const libFile = path.join(libDir, 'utils.js');
const srcFile = path.join(srcDir, 'main.js');
fs.writeFileSync(libFile, `
function libUtility() { return 'lib'; }
`);
fs.writeFileSync(srcFile, `
function srcUtility() { return 'src'; }
`);
const results = await analyzeProjectRefactoring(tempDir, {
targetDirs: ['lib'],
extensions: ['.js']
});
qtests.assert(results.summary.totalFiles >= 1, 'Should analyze files in target directories');
// Clean up
fs.rmSync(tempDir, { recursive: true, force: true });
console.log('✓ analyzeProjectRefactoring correctly handles target directories');
results.passed++;
} catch (error) {
console.log(`✗ analyzeProjectRefactoring target directories test failed: ${error.message}`);
}
// Test barrel file detection
results.total++;
try {
const tempDir = path.join(__dirname, 'temp-barrel-test');
const utilsDir = path.join(tempDir, 'utils');
fs.mkdirSync(tempDir, { recursive: true });
fs.mkdirSync(utilsDir, { recursive: true });
// Create utility files without index.js (missing barrel)
const util1 = path.join(utilsDir, 'format.js');
const util2 = path.join(utilsDir, 'validate.js');
fs.writeFileSync(util1, `
export function formatText(text) { return text.trim(); }
`);
fs.writeFileSync(util2, `
export function validateInput(input) { return input.length > 0; }
`);
const results = await analyzeProjectRefactoring(tempDir, {
includeBarrelFiles: true,
extensions: ['.js']
});
qtests.assert(results.opportunities.some(op => op.type === 'barrel_file'), 'Should detect missing barrel files');
// Clean up
fs.rmSync(tempDir, { recursive: true, force: true });
console.log('✓ analyzeProjectRefactoring correctly detects barrel file opportunities');
results.passed++;
} catch (error) {
console.log(`✗ analyzeProjectRefactoring barrel file test failed: ${error.message}`);
}
// Test organization score calculation
results.total++;
try {
const tempDir = path.join(__dirname, 'temp-score-test');
fs.mkdirSync(tempDir, { recursive: true });
// Create well-organized project
const indexFile = path.join(tempDir, 'index.js');
fs.writeFileSync(indexFile, `
export { utilityA, utilityB } from './utils';
export { ComponentA } from './components';
`);
const utilsFile = path.join(tempDir, 'utils.js');
fs.writeFileSync(utilsFile, `
export function utilityA() { return 'A'; }
export function utilityB() { return 'B'; }
`);
const results = await analyzeProjectRefactoring(tempDir, {
extensions: ['.js']
});
qtests.assert(typeof results.summary.organizationScore === 'number', 'Should calculate organization score');
qtests.assert(results.summary.organizationScore >= 0 && results.summary.organizationScore <= 100, 'Score should be 0-100');
qtests.assert(typeof results.summary.organizationGrade === 'string', 'Should calculate organization grade');
// Clean up
fs.rmSync(tempDir, { recursive: true, force: true });
console.log('✓ analyzeProjectRefactoring correctly calculates organization metrics');
results.passed++;
} catch (error) {
console.log(`✗ analyzeProjectRefactoring organization score test failed: ${error.message}`);
}
// Test error handling for non-existent directory
results.total++;
try {
const results = await analyzeProjectRefactoring('/non/existent/directory', {});
qtests.assert(results.summary.totalFiles === 0, 'Should handle non-existent directory gracefully');
qtests.assert(Array.isArray(results.opportunities), 'Should return empty opportunities array');
console.log('✓ analyzeProjectRefactoring handles non-existent directories gracefully');
results.passed++;
} catch (error) {
console.log(`✗ analyzeProjectRefactoring error handling test failed: ${error.message}`);
}
// Test filtering by extension
results.total++;
try {
const tempDir = path.join(__dirname, 'temp-extension-test');
fs.mkdirSync(tempDir, { recursive: true });
const jsFile = path.join(tempDir, 'utils.js');
const tsFile = path.join(tempDir, 'types.ts');
const txtFile = path.join(tempDir, 'readme.txt');
fs.writeFileSync(jsFile, `function jsUtil() { return 'js'; }`);
fs.writeFileSync(tsFile, `function tsUtil(): string { return 'ts'; }`);
fs.writeFileSync(txtFile, `This is a text file`);
const jsOnlyResults = await analyzeProjectRefactoring(tempDir, {
extensions: ['.js']
});
const multiExtResults = await analyzeProjectRefactoring(tempDir, {
extensions: ['.js', '.ts']
});
qtests.assert(jsOnlyResults.summary.totalFiles === 1, 'Should filter by JS extension only');
qtests.assert(multiExtResults.summary.totalFiles === 2, 'Should include both JS and TS files');
// Clean up
fs.rmSync(tempDir, { recursive: true, force: true });
console.log('✓ analyzeProjectRefactoring correctly filters by file extensions');
results.passed++;
} catch (error) {
console.log(`✗ analyzeProjectRefactoring extension filtering test failed: ${error.message}`);
}
console.log(`=== Project Refactoring Analyzer Test Results ===`);
console.log(`Tests passed: ${results.passed}/${results.total}`);
console.log(`Success rate: ${((results.passed / results.total) * 100).toFixed(1)}%`);
return results;
}
module.exports = { runTests };