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+ / s e t T i m e o u t \s * \( \s * \( \s * \) \s * = > / , // setTimeout(() => {
20+ / s e t T i m e o u t \s * \( \s * f u n c t i o n / , // setTimeout(function() {
21+ / m o c k P r o c e s s \. s t d o u t \. e m i t / , // mockProcess.stdout.emit
22+ / m o c k P r o c e s s \. s t d e r r \. e m i t / , // mockProcess.stderr.emit
23+ / m o c k P r o c e s s \. e m i t \s * \( / , // mockProcess.emit(
24+ / M o c k C h i l d P r o c e s s \s + e x t e n d s \s + E v e n t E m i t t e r / , // class MockChildProcess extends EventEmitter
25+ / n e w \s + E v e n t E m i t t e r \( \) / , // new EventEmitter()
26+ ] ;
27+
28+ // Patterns that indicate the new dependency injection approach
29+ const DEPENDENCY_INJECTION_PATTERNS = [
30+ / c r e a t e M o c k E x e c u t o r / , // createMockExecutor usage
31+ / e x e c u t o r \? \s * : \s * C o m m a n d E x e c u t o r / , // executor?: CommandExecutor parameter
32+ / v i \. f n \( \) \. m o c k R e s o l v e d V a l u e / , // vi.fn().mockResolvedValue for custom executors
33+ / v i \. f n \( \) \. m o c k R e j e c t e d V a l u e / , // 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