77 beforeEach ,
88 afterEach ,
99} from '@jest/globals' ;
10+ import type { ConversionItem } from '../types/audio.js' ;
1011
1112// ESM mocks must be declared BEFORE dynamic imports
1213jest . unstable_mockModule ( 'perf_hooks' , ( ) => ( {
@@ -37,10 +38,13 @@ jest.unstable_mockModule('../utils.js', () => ({
3738 addToLog : jest . fn ( ) ,
3839 settings : { oggCodec : 'vorbis' } ,
3940 initializeFileNames : jest . fn ( ) ,
40- rl : { question : jest . fn ( ( question , callback ) => callback ( ) ) } ,
41+ rl : {
42+ question : jest . fn ( ( _question : string , callback : ( ) => void ) => callback ( ) ) ,
43+ } ,
4144 getAnswer : jest . fn ( ) ,
4245 runtimeBaseDir : '/mock/base' ,
4346 isPackagedRuntime : false ,
47+ findBinary : ( ) => '/mock/base/converterWorker.js' ,
4448} ) ) ;
4549
4650jest . unstable_mockModule ( 'os' , ( ) => ( {
@@ -53,13 +57,26 @@ const os = await import('os');
5357const { convertFiles } = await import ( '../converterManager.js' ) ;
5458import events from 'events' ;
5559
60+ type WorkerHandlers = {
61+ message ?: ( message : unknown ) => void ;
62+ error ?: ( error : unknown ) => void ;
63+ exit ?: ( code : number ) => void ;
64+ } ;
65+
66+ const workerMock = Worker as unknown as jest . MockedFunction < any > ;
67+ const cpusMock = os . cpus as unknown as jest . MockedFunction < typeof os . cpus > ;
68+
5669events . defaultMaxListeners = 20 ;
5770
5871// Add a timeout helper function
59- const withTimeout = ( promise , timeoutMs , errorMessage ) => {
60- let timeoutId ;
61-
62- const timeoutPromise = new Promise ( ( _ , reject ) => {
72+ const withTimeout = < T > (
73+ promise : Promise < T > ,
74+ timeoutMs : number ,
75+ errorMessage ?: string
76+ ) : Promise < T > => {
77+ let timeoutId : ReturnType < typeof setTimeout > | undefined ;
78+
79+ const timeoutPromise : Promise < never > = new Promise ( ( _ , reject ) => {
6380 timeoutId = setTimeout ( ( ) => {
6481 console . error (
6582 `TIMEOUT: ${ errorMessage || 'Test took too long to complete' } `
@@ -73,7 +90,9 @@ const withTimeout = (promise, timeoutMs, errorMessage) => {
7390 } ) ;
7491
7592 return Promise . race ( [ promise , timeoutPromise ] ) . finally ( ( ) => {
76- clearTimeout ( timeoutId ) ;
93+ if ( timeoutId ) {
94+ clearTimeout ( timeoutId ) ;
95+ }
7796 } ) ;
7897} ;
7998
@@ -104,8 +123,8 @@ describe('convertFiles', () => {
104123 } ) ;
105124
106125 // Create a factory function for test files to make tests more maintainable
107- const createTestFiles = ( count = 2 ) => {
108- const files = [ ] ;
126+ const createTestFiles = ( count = 2 ) : ConversionItem [ ] => {
127+ const files : ConversionItem [ ] = [ ] ;
109128 for ( let i = 0 ; i < count ; i ++ ) {
110129 files . push ( {
111130 inputFile : `C:/Music/testfile${ i } .mp3` ,
@@ -125,12 +144,15 @@ describe('convertFiles', () => {
125144 delayCompletion = false ,
126145 } = { } ) {
127146 // Mock events object
128- const handlers = { } ;
147+ const handlers : WorkerHandlers = { } ;
129148
130149 // Create a worker with configurable behavior
131150 const worker = {
132- on : jest . fn ( ( event , handler ) => {
133- handlers [ event ] = handler ;
151+ on : jest . fn ( ( event : string , handler : ( ...args : unknown [ ] ) => void ) => {
152+ if ( event === 'message' ) handlers . message = handler ;
153+ if ( event === 'error' ) handlers . error = handler ;
154+ if ( event === 'exit' )
155+ handlers . exit = handler as unknown as ( code : number ) => void ;
134156
135157 // Don't auto-trigger events if we want to delay completion
136158 if ( delayCompletion ) {
@@ -169,7 +191,7 @@ describe('convertFiles', () => {
169191 it ( 'should process files successfully' , async ( ) => {
170192 // Create a simple mock worker that auto-completes successfully
171193 console . log ( "Starting 'should process files successfully' test" ) ;
172- Worker . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
194+ workerMock . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
173195
174196 const result = await withTimeout (
175197 convertFiles ( createTestFiles ( 2 ) ) ,
@@ -178,15 +200,15 @@ describe('convertFiles', () => {
178200 ) ;
179201 console . log ( "Test 'should process files successfully' completed" ) ;
180202
181- expect ( Worker ) . toHaveBeenCalled ( ) ;
203+ expect ( workerMock ) . toHaveBeenCalled ( ) ;
182204 expect ( result . failedFiles ) . toHaveLength ( 0 ) ;
183205 expect ( result . successfulFiles . length ) . toBeGreaterThan ( 0 ) ;
184206 } , 30000 ) ;
185207
186208 it ( 'should handle failed conversions (non-zero exit code)' , async ( ) => {
187209 // Create a worker that exits with non-zero code
188210 console . log ( "Starting 'should handle failed conversions' test" ) ;
189- Worker . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 1 } ) ) ;
211+ workerMock . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 1 } ) ) ;
190212
191213 const result = await withTimeout (
192214 convertFiles ( createTestFiles ( 1 ) ) ,
@@ -195,7 +217,7 @@ describe('convertFiles', () => {
195217 ) ;
196218 console . log ( "Test 'should handle failed conversions' completed" ) ;
197219
198- expect ( Worker ) . toHaveBeenCalled ( ) ;
220+ expect ( workerMock ) . toHaveBeenCalled ( ) ;
199221 expect ( result . successfulFiles ) . toHaveLength ( 0 ) ;
200222 expect ( result . failedFiles . length ) . toBeGreaterThan ( 0 ) ;
201223 expect ( console . error ) . toHaveBeenCalled ( ) ;
@@ -205,12 +227,15 @@ describe('convertFiles', () => {
205227 // Mock a worker that triggers an error
206228 console . log ( "Starting 'should handle worker errors' test" ) ;
207229
208- Worker . mockImplementation ( ( ) => {
230+ workerMock . mockImplementation ( ( ) => {
209231 // Synchronously fire the error event when registered
210- const handlers = { } ;
232+ const handlers : WorkerHandlers = { } ;
211233 const worker = {
212- on : jest . fn ( ( event , handler ) => {
213- handlers [ event ] = handler ;
234+ on : jest . fn ( ( event : string , handler : ( ...args : unknown [ ] ) => void ) => {
235+ if ( event === 'message' ) handlers . message = handler ;
236+ if ( event === 'error' ) handlers . error = handler ;
237+ if ( event === 'exit' )
238+ handlers . exit = handler as unknown as ( code : number ) => void ;
214239 if ( event === 'error' ) {
215240 handler ( new Error ( 'Worker thread error' ) ) ;
216241 }
@@ -238,7 +263,7 @@ describe('convertFiles', () => {
238263 // Mock a worker that sends stderr messages
239264 console . log ( "Starting 'should handle stderr messages from workers' test" ) ;
240265
241- Worker . mockImplementation ( ( ) =>
266+ workerMock . mockImplementation ( ( ) =>
242267 createWorkerMock ( {
243268 triggerStderr : true ,
244269 } )
@@ -252,7 +277,7 @@ describe('convertFiles', () => {
252277 console . log ( "Test 'should handle stderr messages from workers' completed" ) ;
253278
254279 expect ( console . error ) . toHaveBeenCalledWith (
255- expect . stringContaining ( 'ERROR MESSAGE FROM FFMPEG ' ) ,
280+ expect . stringContaining ( 'FFmpeg stderr ' ) ,
256281 expect . any ( String ) ,
257282 expect . any ( String ) ,
258283 expect . any ( String )
@@ -263,7 +288,7 @@ describe('convertFiles', () => {
263288 // Mock a worker that sends a 'no space left' error
264289 console . log ( "Starting 'should detect disk space errors' test" ) ;
265290
266- Worker . mockImplementation ( ( ) =>
291+ workerMock . mockImplementation ( ( ) =>
267292 createWorkerMock ( {
268293 triggerStderr : true ,
269294 noSpaceLeft : true ,
@@ -272,7 +297,7 @@ describe('convertFiles', () => {
272297
273298 // Mock process.exit
274299 const originalExit = process . exit ;
275- process . exit = jest . fn ( ) ;
300+ process . exit = jest . fn ( ) as unknown as typeof process . exit ;
276301
277302 await withTimeout (
278303 convertFiles ( createTestFiles ( 1 ) ) ,
@@ -296,7 +321,7 @@ describe('convertFiles', () => {
296321 ) ;
297322
298323 let callCount = 0 ;
299- Worker . mockImplementation ( ( ) => {
324+ workerMock . mockImplementation ( ( ) => {
300325 callCount ++ ;
301326 return createWorkerMock ( {
302327 exitCode : callCount % 2 === 0 ? 0 : 1 ,
@@ -312,7 +337,7 @@ describe('convertFiles', () => {
312337 "Test 'should handle multiple workers and files properly' completed"
313338 ) ;
314339
315- expect ( Worker ) . toHaveBeenCalledTimes ( 4 ) ;
340+ expect ( workerMock ) . toHaveBeenCalledTimes ( 4 ) ;
316341 expect ( result . successfulFiles . length ) . toBeGreaterThan ( 0 ) ;
317342 expect ( result . failedFiles . length ) . toBeGreaterThan ( 0 ) ;
318343 } , 30000 ) ;
@@ -322,11 +347,11 @@ describe('convertFiles', () => {
322347 // Mock os.cpus to throw an error
323348 console . log ( "Starting 'should handle CPU detection failure' test" ) ;
324349
325- os . cpus . mockImplementationOnce ( ( ) => {
350+ cpusMock . mockImplementationOnce ( ( ) => {
326351 throw new Error ( 'CPU detection failed' ) ;
327352 } ) ;
328353
329- Worker . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
354+ workerMock . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
330355
331356 const result = await withTimeout (
332357 convertFiles ( createTestFiles ( 2 ) ) ,
@@ -346,8 +371,8 @@ describe('convertFiles', () => {
346371 // Set up 10 CPUs but only 2 files
347372 console . log ( "Starting 'should limit concurrent workers' test" ) ;
348373
349- os . cpus . mockReturnValueOnce ( Array ( 10 ) . fill ( { } ) ) ;
350- Worker . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
374+ cpusMock . mockReturnValueOnce ( Array ( 10 ) . fill ( { } ) ) ;
375+ workerMock . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
351376
352377 await withTimeout (
353378 convertFiles ( createTestFiles ( 2 ) ) ,
@@ -356,7 +381,7 @@ describe('convertFiles', () => {
356381 ) ;
357382 console . log ( "Test 'should limit concurrent workers' completed" ) ;
358383
359- expect ( Worker ) . toHaveBeenCalledTimes ( 2 ) ; // Should create only 2 workers
384+ expect ( workerMock ) . toHaveBeenCalledTimes ( 2 ) ; // Should create only 2 workers
360385 } , 30000 ) ;
361386
362387 it ( 'should properly handle worker creation errors' , async ( ) => {
@@ -365,7 +390,7 @@ describe('convertFiles', () => {
365390 "Starting 'should properly handle worker creation errors' test"
366391 ) ;
367392
368- Worker . mockImplementationOnce ( ( ) => {
393+ workerMock . mockImplementationOnce ( ( ) => {
369394 throw new Error ( 'Failed to create worker' ) ;
370395 } ) ;
371396
@@ -386,7 +411,7 @@ describe('convertFiles', () => {
386411 } , 30000 ) ;
387412
388413 it ( 'should handle an empty file list' , async ( ) => {
389- Worker . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
414+ workerMock . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
390415 const result = await withTimeout (
391416 convertFiles ( [ ] ) ,
392417 5000 ,
@@ -397,10 +422,10 @@ describe('convertFiles', () => {
397422 } ) ;
398423
399424 it ( 'should handle file with missing properties gracefully' , async ( ) => {
400- Worker . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
425+ workerMock . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 0 } ) ) ;
401426 const files = [
402427 { inputFile : undefined , outputFile : undefined , outputFormat : undefined } ,
403- ] ;
428+ ] as unknown as ConversionItem [ ] ;
404429 const result = await withTimeout (
405430 convertFiles ( files ) ,
406431 5000 ,
@@ -410,7 +435,7 @@ describe('convertFiles', () => {
410435 } ) ;
411436
412437 it ( 'should put all files in failedFiles if all workers fail' , async ( ) => {
413- Worker . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 1 } ) ) ;
438+ workerMock . mockImplementation ( ( ) => createWorkerMock ( { exitCode : 1 } ) ) ;
414439 const files = createTestFiles ( 3 ) ;
415440 const result = await withTimeout (
416441 convertFiles ( files ) ,
0 commit comments