UNPKG

@neurolint/cli

Version:

Professional React/Next.js modernization platform with CLI, VS Code, and Web App integrations

320 lines (280 loc) 8.88 kB
#!/usr/bin/env node /** * Layer 6: Testing Fixes * Enhances testing infrastructure and patterns */ const fs = require('fs').promises; const path = require('path'); const BackupManager = require('../backup-manager'); /** * Transform code for testing improvements */ async function transform(code, options = {}) { const { dryRun = false, verbose = false, filePath = process.cwd() } = options; try { let updatedCode = code; const changes = []; const warnings = []; let changeCount = 0; // Test file detection patterns const testPatterns = [ /\.test\.(js|jsx|ts|tsx)$/, /\.spec\.(js|jsx|ts|tsx)$/, /__tests__\//, /test\//, /tests\// ]; const isTestFile = testPatterns.some(pattern => pattern.test(filePath)); if (isTestFile) { // Testing-specific transformations const beforeTesting = updatedCode; // Add testing utilities imports if missing if (!updatedCode.includes('@testing-library/react') && !updatedCode.includes('@testing-library/jest-dom') && updatedCode.includes('render') || updatedCode.includes('screen')) { updatedCode = `import { render, screen } from '@testing-library/react';\nimport '@testing-library/jest-dom';\n\n${updatedCode}`; changes.push({ type: 'TestingImports', description: 'Added testing library imports', location: { line: 1 } }); } // Improve test descriptions updatedCode = updatedCode.replace( /(describe|it|test)\s*\(\s*['"`]([^'"`]+)['"`]/g, (match, testType, description) => { if (description.length < 10) { const improvedDescription = `${description} should work correctly`; changes.push({ type: 'TestDescription', description: `Improved test description: "${description}" -> "${improvedDescription}"`, location: null }); return `${testType}('${improvedDescription}'`; } return match; } ); // Add accessibility testing if (updatedCode.includes('render') && !updatedCode.includes('toBeInTheDocument')) { updatedCode = updatedCode.replace( /(render\s*\(\s*<[^>]+>)/g, (match) => { changes.push({ type: 'AccessibilityTesting', description: 'Added accessibility testing suggestion', location: null }); return `${match}\n // Consider adding: expect(screen.getByRole('button')).toBeInTheDocument();`; } ); } if (updatedCode !== beforeTesting) { changeCount = changes.length; } } // No changes -> fail with expected message if (changeCount === 0) { return { success: false, code, originalCode: code, changeCount: 0, error: 'No changes were made', states: [code], changes, warnings }; } // Dry-run behavior if (dryRun) { return { success: true, code: updatedCode, originalCode: code, changeCount, states: [code, updatedCode], changes, warnings, dryRun: true }; } return { success: true, code: updatedCode, originalCode: code, changeCount, states: [code, updatedCode], changes, warnings }; } catch (error) { return { success: false, code, originalCode: code, changeCount: 0, error: error.message, states: [code], changes: [], warnings: [error.message] }; } } /** * Generate test files for components */ async function generateTestFiles(componentPath, options = {}) { const { dryRun = false, verbose = false } = options; try { const componentContent = await fs.readFile(componentPath, 'utf8'); const componentName = path.basename(componentPath, path.extname(componentPath)); const testPath = componentPath.replace(/\.(jsx?|tsx?)$/, '.test.$1'); // Extract component props from TypeScript interfaces or PropTypes const propsMatch = componentContent.match(/interface\s+(\w+Props)\s*\{([^}]+)\}/); const propTypesMatch = componentContent.match(/PropTypes\.shape\(\{([^}]+)\}\)/); let props = []; if (propsMatch) { props = propsMatch[2].split('\n') .map(line => line.trim()) .filter(line => line.includes(':')) .map(line => { const [name] = line.split(':'); return name.trim(); }); } else if (propTypesMatch) { props = propTypesMatch[1].split('\n') .map(line => line.trim()) .filter(line => line.includes(':')) .map(line => { const [name] = line.split(':'); return name.trim(); }); } const testContent = `import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import ${componentName} from './${componentName}'; describe('${componentName}', () => { it('should render correctly', () => { render(<${componentName} />); expect(screen.getByRole('main')).toBeInTheDocument(); }); it('should handle props correctly', () => { const testProps = { ${props.map(prop => `${prop}: 'test-${prop}'`).join(',\n ')} }; render(<${componentName} {...testProps} />); // Add specific prop testing here }); it('should be accessible', () => { render(<${componentName} />); // Add accessibility testing here expect(screen.getByRole('main')).toBeInTheDocument(); }); }); `; if (dryRun) { return { success: true, testPath, testContent, dryRun: true }; } await fs.writeFile(testPath, testContent); return { success: true, testPath, testContent }; } catch (error) { return { success: false, error: error.message }; } } /** * Setup testing environment */ async function setupTestingEnvironment(projectPath, options = {}) { const { dryRun = false, verbose = false } = options; try { const packageJsonPath = path.join(projectPath, 'package.json'); const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); // Add testing dependencies if missing const testingDeps = { '@testing-library/react': '^13.0.0', '@testing-library/jest-dom': '^5.16.5', '@testing-library/user-event': '^14.0.0', 'jest': '^29.0.0', 'jest-environment-jsdom': '^29.0.0' }; let updated = false; for (const [dep, version] of Object.entries(testingDeps)) { if (!packageJson.devDependencies?.[dep]) { packageJson.devDependencies = { ...packageJson.devDependencies, [dep]: version }; updated = true; } } // Add test scripts if missing if (!packageJson.scripts?.test) { packageJson.scripts = { ...packageJson.scripts, test: 'jest', 'test:watch': 'jest --watch', 'test:coverage': 'jest --coverage' }; updated = true; } if (updated && !dryRun) { await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); } // Create Jest configuration const jestConfigPath = path.join(projectPath, 'jest.config.js'); if (!dryRun) { const jestConfig = `module.exports = { testEnvironment: 'jsdom', setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], moduleNameMapping: { '^@/(.*)$': '<rootDir>/src/$1', }, testMatch: [ '**/__tests__/**/*.(js|jsx|ts|tsx)', '**/*.(test|spec).(js|jsx|ts|tsx)' ], collectCoverageFrom: [ 'src/**/*.(js|jsx|ts|tsx)', '!src/**/*.d.ts', '!src/**/*.stories.(js|jsx|ts|tsx)' ] };`; await fs.writeFile(jestConfigPath, jestConfig); } // Create Jest setup file const jestSetupPath = path.join(projectPath, 'jest.setup.js'); if (!dryRun) { const jestSetup = `import '@testing-library/jest-dom';`; await fs.writeFile(jestSetupPath, jestSetup); } return { success: true, updated, jestConfigPath: dryRun ? 'jest.config.js' : jestConfigPath, jestSetupPath: dryRun ? 'jest.setup.js' : jestSetupPath }; } catch (error) { return { success: false, error: error.message }; } } module.exports = { transform, generateTestFiles, setupTestingEnvironment };