@@ -9,6 +9,7 @@ const isPathInside = require('is-path-inside');
99const { minimatch} = require ( 'minimatch' ) ;
1010const Context = require ( '../context' ) ;
1111const collectFiles = require ( './collect-files' ) ;
12+ const fs = require ( 'node:fs' ) ;
1213
1314/**
1415 * @typedef {import('chokidar').FSWatcher } FSWatcher
@@ -385,6 +386,10 @@ function createPathMatcher(allowed, ignored, basePath) {
385386 return matcher ;
386387}
387388
389+ // grab Date.now before anything like sinon.createFakeTimers can monkeypatch it
390+ // eslint-disable-next-line no-restricted-globals
391+ const dateNow = Date . now ;
392+
388393/**
389394 * Bootstraps a Chokidar watcher. Handles keyboard input & signals
390395 * @param {Mocha } mocha - Mocha instance
@@ -434,26 +439,41 @@ const createWatcher = (
434439 ignored : matcher . ignore
435440 } ) ;
436441
437- const rerunner = createRerunner ( mocha , watcher , {
442+ const rerunner = createRerunner ( mocha , watcher , matcher , {
438443 beforeRun
439444 } ) ;
440445
441- watcher . on ( 'ready' , async ( ) => {
446+ const startTime = dateNow ( ) ;
447+ let ready = false ;
448+ watcher . on ( 'ready' , ( ) => {
442449 debug ( 'watcher ready' ) ;
443- if ( ! globalFixtureContext ) {
444- debug ( 'triggering global setup' ) ;
445- globalFixtureContext = await mocha . runGlobalSetup ( ) ;
446- }
447- rerunner . run ( ) ;
450+ ready = true ;
448451 } ) ;
449452
450- watcher . on ( 'all' , ( _event , filePath ) => {
451- // only allow file paths that match the allowed patterns
452- if ( matcher . allow ( filePath ) ) {
453- rerunner . scheduleRun ( ) ;
453+ watcher . on ( 'all' , async function handleEvent ( event , filePath , stat ) {
454+ if ( exiting || ! matcher . allow ( filePath ) ) return ;
455+ if ( event === 'unlink' || ( stat ? stat . mtime > startTime : ready ) ) {
456+ // we don't want to accidentally trigger a run before globalFixtureContext
457+ // has been created. If it hasn't then it's okay to do nothing here because
458+ // the code that creates globalFixtureContext will run the tests afterward
459+ if ( globalFixtureContext ) {
460+ rerunner . scheduleRun ( ) ;
461+ }
462+ return ;
463+ }
464+ if ( ! ready && ! stat ) {
465+ fs . stat ( filePath , ( err , stat ) => {
466+ if ( stat ) handleEvent ( event , filePath , stat ) ;
467+ } ) ;
454468 }
455469 } ) ;
456470
471+ debug ( 'triggering global setup' ) ;
472+ mocha . runGlobalSetup ( ) . then ( context => {
473+ globalFixtureContext = context ;
474+ rerunner . run ( ) ;
475+ } ) ;
476+
457477 hideCursor ( ) ;
458478 process . on ( 'exit' , ( ) => {
459479 showCursor ( ) ;
@@ -509,13 +529,14 @@ const createWatcher = (
509529 *
510530 * @param {Mocha } mocha - Mocha instance
511531 * @param {FSWatcher } watcher - Chokidar `FSWatcher` instance
532+ * @param {PathMatcher } matcher - `PathMatcher` instance
512533 * @param {Object } [opts] - Options!
513534 * @param {BeforeWatchRun } [opts.beforeRun] - Function to call before `mocha.run()`
514535 * @returns {Rerunner }
515536 * @ignore
516537 * @private
517538 */
518- const createRerunner = ( mocha , watcher , { beforeRun} = { } ) => {
539+ const createRerunner = ( mocha , watcher , matcher , { beforeRun} = { } ) => {
519540 // Set to a `Runner` when mocha is running. Set to `null` when mocha is not
520541 // running.
521542 let runner = null ;
@@ -529,7 +550,7 @@ const createRerunner = (mocha, watcher, {beforeRun} = {}) => {
529550 runner = mocha . run ( ( ) => {
530551 debug ( 'finished watch run' ) ;
531552 runner = null ;
532- blastCache ( watcher ) ;
553+ blastCache ( matcher ) ;
533554 if ( rerunScheduled ) {
534555 rerun ( ) ;
535556 } else {
@@ -566,25 +587,6 @@ const createRerunner = (mocha, watcher, {beforeRun} = {}) => {
566587 } ;
567588} ;
568589
569- /**
570- * Return the list of absolute paths watched by a Chokidar watcher.
571- *
572- * @param watcher - Instance of a Chokidar watcher
573- * @return {string[] } - List of absolute paths
574- * @ignore
575- * @private
576- */
577- const getWatchedFiles = watcher => {
578- const watchedDirs = watcher . getWatched ( ) ;
579- return Object . keys ( watchedDirs ) . reduce (
580- ( acc , dir ) => [
581- ...acc ,
582- ...watchedDirs [ dir ] . map ( file => path . join ( dir , file ) )
583- ] ,
584- [ ]
585- ) ;
586- } ;
587-
588590/**
589591 * Hide the cursor.
590592 * @ignore
@@ -613,14 +615,17 @@ const eraseLine = () => {
613615
614616/**
615617 * Blast all of the watched files out of `require.cache`
616- * @param {FSWatcher } watcher - Chokidar FSWatcher
618+ * @param {PathMatcher } matcher - `PathMatcher` instance
617619 * @ignore
618620 * @private
619621 */
620- const blastCache = watcher => {
621- const files = getWatchedFiles ( watcher ) ;
622- files . forEach ( file => {
623- delete require . cache [ file ] ;
624- } ) ;
625- debug ( 'deleted %d file(s) from the require cache' , files . length ) ;
622+ const blastCache = matcher => {
623+ let deletedCount = 0 ;
624+ for ( const key in require . cache ) {
625+ if ( matcher . allow ( key ) ) {
626+ deletedCount ++ ;
627+ delete require . cache [ key ] ;
628+ }
629+ }
630+ debug ( 'deleted %d file(s) from the require cache' , deletedCount ) ;
626631} ;
0 commit comments