Skip to content

Commit 54f0dd4

Browse files
committed
fix: convert test_device_proj.test.ts to dependency injection pattern
- Converted 24 setTimeout patterns to deterministic dependency injection - Updated plugin to accept CommandExecutor parameter - Replaced MockChildProcess with createMockExecutor - All 11 tests passing with fast execution (5ms) - Follows CLAUDE.md testing standards
1 parent 3415843 commit 54f0dd4

17 files changed

Lines changed: 1157 additions & 755 deletions

scripts/find-timeout-tests.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Script to find all test files that use setTimeout-based mocking approach
5+
* These tests need to be converted to dependency injection pattern
6+
*/
7+
8+
import { readFileSync, readdirSync, statSync } from 'fs';
9+
import { join, relative } from 'path';
10+
import { fileURLToPath } from 'url';
11+
import { dirname } from 'path';
12+
13+
const __filename = fileURLToPath(import.meta.url);
14+
const __dirname = dirname(__filename);
15+
const projectRoot = join(__dirname, '..');
16+
17+
// Patterns that indicate setTimeout-based mocking approach
18+
const TIMEOUT_PATTERNS = [
19+
/setTimeout\s*\(\s*\(\s*\)\s*=>/, // setTimeout(() => {
20+
/setTimeout\s*\(\s*function/, // setTimeout(function() {
21+
/mockProcess\.stdout\.emit/, // mockProcess.stdout.emit
22+
/mockProcess\.stderr\.emit/, // mockProcess.stderr.emit
23+
/mockProcess\.emit\s*\(/, // mockProcess.emit(
24+
/MockChildProcess\s+extends\s+EventEmitter/, // class MockChildProcess extends EventEmitter
25+
/new\s+EventEmitter\(\)/, // new EventEmitter()
26+
];
27+
28+
// Patterns that indicate the new dependency injection approach
29+
const DEPENDENCY_INJECTION_PATTERNS = [
30+
/createMockExecutor/, // createMockExecutor usage
31+
/executor\?\s*:\s*CommandExecutor/, // executor?: CommandExecutor parameter
32+
/vi\.fn\(\)\.mockResolvedValue/, // vi.fn().mockResolvedValue for custom executors
33+
/vi\.fn\(\)\.mockRejectedValue/, // vi.fn().mockRejectedValue for custom executors
34+
];
35+
36+
function findTestFiles(dir) {
37+
const testFiles = [];
38+
39+
function traverse(currentDir) {
40+
const items = readdirSync(currentDir);
41+
42+
for (const item of items) {
43+
const fullPath = join(currentDir, item);
44+
const stat = statSync(fullPath);
45+
46+
if (stat.isDirectory()) {
47+
// Skip node_modules and other non-relevant directories
48+
if (!item.startsWith('.') && item !== 'node_modules' && item !== 'dist' && item !== 'build') {
49+
traverse(fullPath);
50+
}
51+
} else if (item.endsWith('.test.ts') || item.endsWith('.test.js')) {
52+
testFiles.push(fullPath);
53+
}
54+
}
55+
}
56+
57+
traverse(dir);
58+
return testFiles;
59+
}
60+
61+
function analyzeTestFile(filePath) {
62+
try {
63+
const content = readFileSync(filePath, 'utf8');
64+
const relativePath = relative(projectRoot, filePath);
65+
66+
// Check for setTimeout patterns
67+
const hasTimeoutPatterns = TIMEOUT_PATTERNS.some(pattern => pattern.test(content));
68+
69+
// Check for dependency injection patterns
70+
const hasDIPatterns = DEPENDENCY_INJECTION_PATTERNS.some(pattern => pattern.test(content));
71+
72+
// Extract specific setTimeout occurrences for details
73+
const timeoutDetails = [];
74+
const lines = content.split('\n');
75+
76+
lines.forEach((line, index) => {
77+
TIMEOUT_PATTERNS.forEach(pattern => {
78+
if (pattern.test(line)) {
79+
timeoutDetails.push({
80+
line: index + 1,
81+
content: line.trim(),
82+
pattern: pattern.source
83+
});
84+
}
85+
});
86+
});
87+
88+
return {
89+
filePath: relativePath,
90+
hasTimeoutPatterns,
91+
hasDIPatterns,
92+
timeoutDetails,
93+
needsConversion: hasTimeoutPatterns && !hasDIPatterns,
94+
isConverted: hasDIPatterns && !hasTimeoutPatterns,
95+
isMixed: hasTimeoutPatterns && hasDIPatterns
96+
};
97+
} catch (error) {
98+
console.error(`Error reading file ${filePath}: ${error.message}`);
99+
return null;
100+
}
101+
}
102+
103+
function main() {
104+
console.log('🔍 Finding test files that need timeout-to-dependency-injection conversion...\n');
105+
106+
const testFiles = findTestFiles(join(projectRoot, 'src'));
107+
const results = testFiles.map(analyzeTestFile).filter(Boolean);
108+
109+
const needsConversion = results.filter(r => r.needsConversion);
110+
const converted = results.filter(r => r.isConverted);
111+
const mixed = results.filter(r => r.isMixed);
112+
const noTimeouts = results.filter(r => !r.hasTimeoutPatterns && !r.hasDIPatterns);
113+
114+
console.log(`📊 ANALYSIS SUMMARY`);
115+
console.log(`==================`);
116+
console.log(`Total test files analyzed: ${results.length}`);
117+
console.log(`❌ Need conversion (setTimeout-based): ${needsConversion.length}`);
118+
console.log(`✅ Already converted (dependency injection): ${converted.length}`);
119+
console.log(`⚠️ Mixed (both patterns): ${mixed.length}`);
120+
console.log(`📝 No timeout patterns: ${noTimeouts.length}`);
121+
console.log('');
122+
123+
if (needsConversion.length > 0) {
124+
console.log(`❌ FILES THAT NEED CONVERSION (${needsConversion.length}):`);
125+
console.log(`=====================================`);
126+
needsConversion.forEach((result, index) => {
127+
console.log(`${index + 1}. ${result.filePath}`);
128+
if (result.timeoutDetails.length > 0) {
129+
result.timeoutDetails.slice(0, 3).forEach(detail => { // Show first 3 occurrences
130+
console.log(` Line ${detail.line}: ${detail.content}`);
131+
});
132+
if (result.timeoutDetails.length > 3) {
133+
console.log(` ... and ${result.timeoutDetails.length - 3} more occurrences`);
134+
}
135+
}
136+
console.log('');
137+
});
138+
}
139+
140+
if (mixed.length > 0) {
141+
console.log(`⚠️ FILES WITH MIXED PATTERNS (${mixed.length}):`);
142+
console.log(`===================================`);
143+
mixed.forEach((result, index) => {
144+
console.log(`${index + 1}. ${result.filePath}`);
145+
console.log(` ⚠️ Contains both setTimeout and dependency injection patterns`);
146+
console.log('');
147+
});
148+
}
149+
150+
if (converted.length > 0) {
151+
console.log(`✅ SUCCESSFULLY CONVERTED FILES (${converted.length}):`);
152+
console.log(`====================================`);
153+
converted.forEach((result, index) => {
154+
console.log(`${index + 1}. ${result.filePath}`);
155+
});
156+
console.log('');
157+
}
158+
159+
// Summary for next steps
160+
if (needsConversion.length > 0) {
161+
console.log(`🎯 NEXT STEPS:`);
162+
console.log(`=============`);
163+
console.log(`1. Convert ${needsConversion.length} files from setTimeout to dependency injection`);
164+
console.log(`2. Run this script again after each fix to track progress`);
165+
console.log(`3. Focus on files with the most timeout patterns first`);
166+
console.log('');
167+
168+
// Show top files by timeout pattern count
169+
const sortedByPatterns = needsConversion
170+
.sort((a, b) => b.timeoutDetails.length - a.timeoutDetails.length)
171+
.slice(0, 5);
172+
173+
console.log(`🎯 TOP 5 FILES BY TIMEOUT PATTERN COUNT:`);
174+
sortedByPatterns.forEach((result, index) => {
175+
console.log(`${index + 1}. ${result.filePath} (${result.timeoutDetails.length} patterns)`);
176+
});
177+
} else if (mixed.length === 0) {
178+
console.log(`🎉 ALL FILES CONVERTED!`);
179+
console.log(`======================`);
180+
console.log(`All test files have been successfully converted to dependency injection pattern.`);
181+
}
182+
183+
// Exit with appropriate code
184+
process.exit(needsConversion.length > 0 || mixed.length > 0 ? 1 : 0);
185+
}
186+
187+
main();

0 commit comments

Comments
 (0)