From 2615a183863dab88da73b718d4ee7f02db5c72a5 Mon Sep 17 00:00:00 2001 From: Kartones Date: Thu, 30 May 2024 09:27:03 +0200 Subject: [PATCH 1/6] refactor: extract handleRequires() to file --- lib/cli/handle-requires.js | 38 +++++++++++++++++++++++++++++++++++ lib/cli/run-helpers.js | 35 -------------------------------- lib/cli/run.js | 8 ++------ lib/nodejs/worker.js | 3 ++- test/node-unit/worker.spec.js | 12 ++++++++--- 5 files changed, 51 insertions(+), 45 deletions(-) create mode 100644 lib/cli/handle-requires.js diff --git a/lib/cli/handle-requires.js b/lib/cli/handle-requires.js new file mode 100644 index 0000000000..e2e447e098 --- /dev/null +++ b/lib/cli/handle-requires.js @@ -0,0 +1,38 @@ +'use strict'; +const fs = require('fs'); +const path = require('path'); +const debug = require('debug')('mocha:cli:handle:requires'); +const {requireOrImport} = require('../nodejs/esm-utils'); +const PluginLoader = require('../plugin-loader'); + +/** + * `require()` the modules as required by `--require `. + * + * Returns array of `mochaHooks` exports, if any. + * @param {string[]} requires - Modules to require + * @returns {Promise} Plugin implementations + * @private + */ +exports.handleRequires = async (requires = [], {ignoredPlugins = []} = {}) => { + const pluginLoader = PluginLoader.create({ignore: ignoredPlugins}); + for await (const mod of requires) { + let modpath = mod; + // this is relative to cwd + if (fs.existsSync(mod) || fs.existsSync(`${mod}.js`)) { + modpath = path.resolve(mod); + debug('resolved required file %s to %s', mod, modpath); + } + const requiredModule = await requireOrImport(modpath); + if (requiredModule && typeof requiredModule === 'object') { + if (pluginLoader.load(requiredModule)) { + debug('found one or more plugin implementations in %s', modpath); + } + } + debug('loaded required module "%s"', mod); + } + const plugins = await pluginLoader.finalize(); + if (Object.keys(plugins).length) { + debug('finalized plugin implementations: %O', plugins); + } + return plugins; +}; diff --git a/lib/cli/run-helpers.js b/lib/cli/run-helpers.js index 078ca7e434..288ddad70c 100644 --- a/lib/cli/run-helpers.js +++ b/lib/cli/run-helpers.js @@ -7,15 +7,12 @@ * @private */ -const fs = require('fs'); const path = require('path'); const debug = require('debug')('mocha:cli:run:helpers'); const {watchRun, watchParallelRun} = require('./watch-run'); const collectFiles = require('./collect-files'); const {format} = require('util'); const {createInvalidLegacyPluginError} = require('../errors'); -const {requireOrImport} = require('../nodejs/esm-utils'); -const PluginLoader = require('../plugin-loader'); /** * Exits Mocha when tests + code under test has finished execution (default) @@ -74,38 +71,6 @@ const exitMocha = code => { exports.list = str => Array.isArray(str) ? exports.list(str.join(',')) : str.split(/ *, */); -/** - * `require()` the modules as required by `--require `. - * - * Returns array of `mochaHooks` exports, if any. - * @param {string[]} requires - Modules to require - * @returns {Promise} Plugin implementations - * @private - */ -exports.handleRequires = async (requires = [], {ignoredPlugins = []} = {}) => { - const pluginLoader = PluginLoader.create({ignore: ignoredPlugins}); - for await (const mod of requires) { - let modpath = mod; - // this is relative to cwd - if (fs.existsSync(mod) || fs.existsSync(`${mod}.js`)) { - modpath = path.resolve(mod); - debug('resolved required file %s to %s', mod, modpath); - } - const requiredModule = await requireOrImport(modpath); - if (requiredModule && typeof requiredModule === 'object') { - if (pluginLoader.load(requiredModule)) { - debug('found one or more plugin implementations in %s', modpath); - } - } - debug('loaded required module "%s"', mod); - } - const plugins = await pluginLoader.finalize(); - if (Object.keys(plugins).length) { - debug('finalized plugin implementations: %O', plugins); - } - return plugins; -}; - /** * Collect and load test files, then run mocha instance. * @param {Mocha} mocha - Mocha instance diff --git a/lib/cli/run.js b/lib/cli/run.js index 66c8cbbb66..988264466a 100644 --- a/lib/cli/run.js +++ b/lib/cli/run.js @@ -16,12 +16,8 @@ const { createMissingArgumentError } = require('../errors'); -const { - list, - handleRequires, - validateLegacyPlugin, - runMocha -} = require('./run-helpers'); +const {list, validateLegacyPlugin, runMocha} = require('./run-helpers'); +const {handleRequires} = require('./handle-requires'); const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones'); const debug = require('debug')('mocha:cli:run'); const defaults = require('../mocharc'); diff --git a/lib/nodejs/worker.js b/lib/nodejs/worker.js index cf5655e80d..f6f5095b70 100644 --- a/lib/nodejs/worker.js +++ b/lib/nodejs/worker.js @@ -12,7 +12,8 @@ const { } = require('../errors'); const workerpool = require('workerpool'); const Mocha = require('../mocha'); -const {handleRequires, validateLegacyPlugin} = require('../cli/run-helpers'); +const {validateLegacyPlugin} = require('../cli/run-helpers'); +const {handleRequires} = require('../cli/handle-requires'); const d = require('debug'); const debug = d.debug(`mocha:parallel:worker:${process.pid}`); const isDebugEnabled = d.enabled(`mocha:parallel:worker:${process.pid}`); diff --git a/test/node-unit/worker.spec.js b/test/node-unit/worker.spec.js index 8a7d73dca7..8fd09126e4 100644 --- a/test/node-unit/worker.spec.js +++ b/test/node-unit/worker.spec.js @@ -54,10 +54,13 @@ describe('worker', function () { }; stubs.runHelpers = { - handleRequires: sinon.stub().resolves({}), validateLegacyPlugin: sinon.stub() }; + stubs.handleRequires = { + handleRequires: sinon.stub().resolves({}) + }; + stubs.plugin = { aggregateRootHooks: sinon.stub().resolves() }; @@ -67,6 +70,7 @@ describe('worker', function () { '../../lib/mocha': stubs.Mocha, '../../lib/nodejs/serializer': stubs.serializer, '../../lib/cli/run-helpers': stubs.runHelpers, + '../../lib/cli/handle-requires': stubs.handleRequires, '../../lib/plugin-loader': stubs.plugin }); }); @@ -148,7 +152,7 @@ describe('worker', function () { serializeJavascript({require: 'foo'}) ); expect( - stubs.runHelpers.handleRequires, + stubs.handleRequires.handleRequires, 'to have a call satisfying', [ 'foo', @@ -217,9 +221,11 @@ describe('worker', function () { await worker.run('some-other-file.js'); expect(stubs.runHelpers, 'to satisfy', { - handleRequires: expect.it('was called once'), validateLegacyPlugin: expect.it('was called once') }); + expect(stubs.handleRequires, 'to satisfy', { + handleRequires: expect.it('was called once') + }); }); }); }); From 55d042e0242fa19324f116a3af6fb4066e5a85c1 Mon Sep 17 00:00:00 2001 From: Kartones Date: Thu, 30 May 2024 13:58:51 +0200 Subject: [PATCH 2/6] fix: full import/require cache blast when running non-parallel watch --- lib/cli/watch-run.js | 44 +++++++++---- lib/nodejs/esm-utils.js | 10 ++- .../watch/dependency-with-state.fixture.js | 11 ++++ .../watch/hook-mutating-dependency.fixture.js | 9 +++ .../watch/test-mutating-dependency.fixture.js | 9 +++ ...st-with-hook-mutated-dependency.fixture.js | 17 +++++ test/integration/options/watch.spec.js | 65 +++++++++++++++++++ 7 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 test/integration/fixtures/options/watch/dependency-with-state.fixture.js create mode 100644 test/integration/fixtures/options/watch/hook-mutating-dependency.fixture.js create mode 100644 test/integration/fixtures/options/watch/test-mutating-dependency.fixture.js create mode 100644 test/integration/fixtures/options/watch/test-with-hook-mutated-dependency.fixture.js diff --git a/lib/cli/watch-run.js b/lib/cli/watch-run.js index a77ed7a91a..857fef35da 100644 --- a/lib/cli/watch-run.js +++ b/lib/cli/watch-run.js @@ -6,7 +6,9 @@ const path = require('path'); const chokidar = require('chokidar'); const Context = require('../context'); const collectFiles = require('./collect-files'); - +const {handleRequires} = require('./handle-requires'); +const {setCacheBusterKey} = require('../nodejs/esm-utils'); +const {uniqueID} = require('../utils'); /** * Exports the `watchRun` function that runs mocha in "watch" mode. * @see module:lib/cli/run-helpers @@ -97,9 +99,14 @@ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => { return createWatcher(mocha, { watchFiles, watchIgnore, - beforeRun({mocha}) { + async beforeRun({mocha}) { mocha.unloadFiles(); + // Reload hooks. If not done, global hooks keep their state between watch runs, but test files always get a new + // instance, making state mutation impossible via global hooks. + const plugins = await handleRequires(mocha.options.require); + mocha.options.rootHooks = plugins.rootHooks; + // I don't know why we're cloning the root suite. const rootSuite = mocha.suite.clone(); @@ -257,13 +264,17 @@ const createRerunner = (mocha, watcher, {beforeRun} = {}) => { // true if a file has changed during a test run let rerunScheduled = false; - const run = () => { + const run = async () => { try { - mocha = beforeRun ? beforeRun({mocha, watcher}) || mocha : mocha; + mocha = beforeRun ? (await beforeRun({mocha, watcher})) || mocha : mocha; + const blastFullCache = !mocha.options.parallel; runner = mocha.run(() => { debug('finished watch run'); runner = null; - blastCache(watcher); + if (blastFullCache) { + setCacheBusterKey(uniqueID()); + } + blastCache(watcher, blastFullCache); if (rerunScheduled) { rerun(); } else { @@ -348,15 +359,26 @@ const eraseLine = () => { /** * Blast all of the watched files out of `require.cache` * @param {FSWatcher} watcher - chokidar FSWatcher + * @param {boolean} blastAll * @ignore * @private */ -const blastCache = watcher => { - const files = getWatchedFiles(watcher); - files.forEach(file => { - delete require.cache[file]; - }); - debug('deleted %d file(s) from the require cache', files.length); +const blastCache = (watcher, blastAll) => { + if (blastAll) { + Object.keys(require.cache) + // Avoid deleting mocha binary (at minimum, breaks Mocha's watch tests) + .filter(file => !file.includes('/mocha/bin/')) + .forEach(file => { + delete require.cache[file]; + }); + debug('deleted all files from the require cache'); + } else { + const files = getWatchedFiles(watcher); + files.forEach(file => { + delete require.cache[file]; + }); + debug('deleted %d file(s) from the require cache', files.length); + } }; /** diff --git a/lib/nodejs/esm-utils.js b/lib/nodejs/esm-utils.js index 5318099365..ce707254b6 100644 --- a/lib/nodejs/esm-utils.js +++ b/lib/nodejs/esm-utils.js @@ -3,6 +3,12 @@ const url = require('url'); const forward = x => x; +let cacheBusterKey = ''; + +exports.setCacheBusterKey = key => { + cacheBusterKey = key; +}; + const formattedImport = async (file, esmDecorator = forward) => { if (path.isAbsolute(file)) { try { @@ -32,7 +38,9 @@ const formattedImport = async (file, esmDecorator = forward) => { return exports.doImport(esmDecorator(file)); }; -exports.doImport = async file => import(file); +// When changing the key, old items won't be garbage collected, but there is no alternative with import(). +// More info: https://github.com/nodejs/node/issues/49442 +exports.doImport = async file => import(`${file}?cache=${cacheBusterKey}`); exports.requireOrImport = async (file, esmDecorator) => { if (path.extname(file) === '.mjs') { diff --git a/test/integration/fixtures/options/watch/dependency-with-state.fixture.js b/test/integration/fixtures/options/watch/dependency-with-state.fixture.js new file mode 100644 index 0000000000..093b027810 --- /dev/null +++ b/test/integration/fixtures/options/watch/dependency-with-state.fixture.js @@ -0,0 +1,11 @@ +let flag = false; + +module.exports.getFlag = () => flag; + +module.exports.enableFlag = () => { + flag = true; +}; + +module.exports.disableFlag = () => { + flag = false; +}; diff --git a/test/integration/fixtures/options/watch/hook-mutating-dependency.fixture.js b/test/integration/fixtures/options/watch/hook-mutating-dependency.fixture.js new file mode 100644 index 0000000000..247378bfd4 --- /dev/null +++ b/test/integration/fixtures/options/watch/hook-mutating-dependency.fixture.js @@ -0,0 +1,9 @@ +const dependency = require('./lib/dependency-with-state'); + +module.exports = { + mochaHooks: { + beforeEach: () => { + dependency.enableFlag(); + } + } +}; diff --git a/test/integration/fixtures/options/watch/test-mutating-dependency.fixture.js b/test/integration/fixtures/options/watch/test-mutating-dependency.fixture.js new file mode 100644 index 0000000000..4357be7d64 --- /dev/null +++ b/test/integration/fixtures/options/watch/test-mutating-dependency.fixture.js @@ -0,0 +1,9 @@ +const dependency = require('./lib/dependency-with-state'); + +it('checks and mutates dependency', () => { + if (dependency.getFlag()) { + throw new Error('test failed'); + } + // Will pass 1st run, fail on subsequent ones + dependency.enableFlag(); +}); diff --git a/test/integration/fixtures/options/watch/test-with-hook-mutated-dependency.fixture.js b/test/integration/fixtures/options/watch/test-with-hook-mutated-dependency.fixture.js new file mode 100644 index 0000000000..b8086ae55d --- /dev/null +++ b/test/integration/fixtures/options/watch/test-with-hook-mutated-dependency.fixture.js @@ -0,0 +1,17 @@ +const dependency = require('./lib/dependency-with-state'); + +// Will fail 1st run, unless hook runs +before(() => { + dependency.disableFlag(); +}); + +// Will pass 1st run, fail on subsequent ones, unless hook runs +afterEach(() => { + dependency.disableFlag(); +}); + +it('hook should have mutated dependency', () => { + if (!dependency.getFlag()) { + throw new Error('test failed'); + } +}); diff --git a/test/integration/options/watch.spec.js b/test/integration/options/watch.spec.js index 957b4938c3..457f6a5e4d 100644 --- a/test/integration/options/watch.spec.js +++ b/test/integration/options/watch.spec.js @@ -316,6 +316,71 @@ describe('--watch', function () { }); }); + // Regression test for https://github.com/mochajs/mocha/issues/5149 + it('reloads all required dependencies between runs', function () { + const testFile = path.join(tempDir, 'test-mutating-dependency.js'); + copyFixture('options/watch/test-mutating-dependency', testFile); + + const dependency = path.join(tempDir, 'lib', 'dependency-with-state.js'); + copyFixture('options/watch/dependency-with-state', dependency); + + // Notice we are watching only the test file, skipping the dependency file. + // This is a simplification of a scenario where there's an unwatched file somewhere in the dependency tree + // that is mutated between runs, and not properly reset. + return runMochaWatchJSONAsync( + [testFile, '--watch-files', 'test-mutating-dependency.js'], + tempDir, + () => { + replaceFileContents( + testFile, + '// Will pass 1st run, fail on subsequent ones', + '// Will pass 1st run, fail on subsequent runs' + ); + } + ).then(results => { + expect(results, 'to have length', 2); + expect(results[0].passes, 'to have length', 1); + expect(results[0].failures, 'to have length', 0); + expect(results[1].passes, 'to have length', 1); + expect(results[1].failures, 'to have length', 0); + }); + }); + + // Regression test for https://github.com/mochajs/mocha/issues/5149 + it('reloads all required dependencies between runs when mutated from a hook', function () { + const testFile = path.join( + tempDir, + 'test-with-hook-mutated-dependency.js' + ); + copyFixture('options/watch/test-with-hook-mutated-dependency', testFile); + + const dependency = path.join(tempDir, 'lib', 'dependency-with-state.js'); + copyFixture('options/watch/dependency-with-state', dependency); + + const hookFile = path.join(tempDir, 'hook-mutating-dependency.js'); + copyFixture('options/watch/hook-mutating-dependency', hookFile); + + return runMochaWatchJSONAsync( + [ + testFile, + '--require', + hookFile, + '--watch-files', + 'test-with-hook-mutated-dependency.js' + ], + tempDir, + () => { + touchFile(testFile); + } + ).then(results => { + expect(results.length, 'to equal', 2); + expect(results[0].passes, 'to have length', 1); + expect(results[0].failures, 'to have length', 0); + expect(results[1].passes, 'to have length', 1); + expect(results[1].failures, 'to have length', 0); + }); + }); + // Regression test for https://github.com/mochajs/mocha/issues/2027 it('respects --fgrep on re-runs', async function () { const testFile = path.join(tempDir, 'test.js'); From 24aaa001911cd78da5dc38898f2443121491cfbf Mon Sep 17 00:00:00 2001 From: Kartones Date: Tue, 4 Jun 2024 10:45:52 +0200 Subject: [PATCH 3/6] fix: don't use disposed mocha instance to query parallel mode --- lib/cli/watch-run.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cli/watch-run.js b/lib/cli/watch-run.js index 857fef35da..edddbe00ba 100644 --- a/lib/cli/watch-run.js +++ b/lib/cli/watch-run.js @@ -261,13 +261,14 @@ const createRerunner = (mocha, watcher, {beforeRun} = {}) => { // running. let runner = null; + const blastFullCache = !mocha.options.parallel; + // true if a file has changed during a test run let rerunScheduled = false; const run = async () => { try { mocha = beforeRun ? (await beforeRun({mocha, watcher})) || mocha : mocha; - const blastFullCache = !mocha.options.parallel; runner = mocha.run(() => { debug('finished watch run'); runner = null; From 7f265974e11483c75491f51720f2570fdc69d140 Mon Sep 17 00:00:00 2001 From: Kartones Date: Tue, 4 Jun 2024 13:16:32 +0200 Subject: [PATCH 4/6] fix: proper import() cache busting + not do empty cache key busting --- lib/nodejs/esm-utils.js | 26 +++++++++++++++++++++----- test/node-unit/esm-utils.spec.js | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/nodejs/esm-utils.js b/lib/nodejs/esm-utils.js index ce707254b6..a41f012c71 100644 --- a/lib/nodejs/esm-utils.js +++ b/lib/nodejs/esm-utils.js @@ -5,6 +5,22 @@ const forward = x => x; let cacheBusterKey = ''; +// When changing the key, old items won't be garbage collected, but there is no alternative with import(). +// More info: https://github.com/nodejs/node/issues/49442 +const withCacheBuster = x => { + if (!cacheBusterKey) { + return x; + } + if (typeof x === 'string') { + return x.includes('?') + ? `${x}&cache=${cacheBusterKey}` + : `${x}?cache=${cacheBusterKey}`; + } else if (x instanceof url.URL) { + x.searchParams.append('cache', cacheBusterKey); + } + return x; +}; + exports.setCacheBusterKey = key => { cacheBusterKey = key; }; @@ -12,7 +28,9 @@ exports.setCacheBusterKey = key => { const formattedImport = async (file, esmDecorator = forward) => { if (path.isAbsolute(file)) { try { - return await exports.doImport(esmDecorator(url.pathToFileURL(file))); + return await exports.doImport( + withCacheBuster(esmDecorator(url.pathToFileURL(file))) + ); } catch (err) { // This is a hack created because ESM in Node.js (at least in Node v15.5.1) does not emit // the location of the syntax error in the error thrown. @@ -35,12 +53,10 @@ const formattedImport = async (file, esmDecorator = forward) => { throw err; } } - return exports.doImport(esmDecorator(file)); + return exports.doImport(withCacheBuster(esmDecorator(file))); }; -// When changing the key, old items won't be garbage collected, but there is no alternative with import(). -// More info: https://github.com/nodejs/node/issues/49442 -exports.doImport = async file => import(`${file}?cache=${cacheBusterKey}`); +exports.doImport = async file => import(file); exports.requireOrImport = async (file, esmDecorator) => { if (path.extname(file) === '.mjs') { diff --git a/test/node-unit/esm-utils.spec.js b/test/node-unit/esm-utils.spec.js index 8880b5bceb..1a4d78172e 100644 --- a/test/node-unit/esm-utils.spec.js +++ b/test/node-unit/esm-utils.spec.js @@ -11,6 +11,7 @@ describe('esm-utils', function () { afterEach(function () { sinon.restore(); + esmUtils.setCacheBusterKey(''); }); describe('loadFilesAsync()', function () { @@ -42,5 +43,22 @@ describe('esm-utils', function () { `${url.pathToFileURL('/foo/bar.mjs').toString()}?foo=bar` ); }); + + it('should decorate imported module with passed decorator - with cache buster key', async function () { + esmUtils.setCacheBusterKey('1234'); + + await esmUtils.loadFilesAsync( + ['/foo/bar.mjs'], + () => {}, + () => {}, + x => `${x}?foo=bar` + ); + + expect( + esmUtils.doImport.firstCall.args[0].toString(), + 'to be', + `${url.pathToFileURL('/foo/bar.mjs').toString()}?foo=bar&cache=1234` + ); + }); }); }); From 9df2bf1df0306a2cd4100a1d0eec12e5179307c0 Mon Sep 17 00:00:00 2001 From: Kartones Date: Sun, 7 Jul 2024 22:26:40 +0200 Subject: [PATCH 5/6] fix: specific mocha binary filters when blasting cache --- lib/cli/watch-run.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/cli/watch-run.js b/lib/cli/watch-run.js index edddbe00ba..f8102bfff6 100644 --- a/lib/cli/watch-run.js +++ b/lib/cli/watch-run.js @@ -367,8 +367,12 @@ const eraseLine = () => { const blastCache = (watcher, blastAll) => { if (blastAll) { Object.keys(require.cache) - // Avoid deleting mocha binary (at minimum, breaks Mocha's watch tests) - .filter(file => !file.includes('/mocha/bin/')) + // Avoid deleting mocha binary + .filter( + file => + !file.includes('mocha/bin/mocha.js') && + !file.includes('mocha/lib/mocha.js') + ) .forEach(file => { delete require.cache[file]; }); From 8dfc1b4075542128c918c16aec1d23db57457adc Mon Sep 17 00:00:00 2001 From: Kartones Date: Sun, 7 Jul 2024 23:00:47 +0200 Subject: [PATCH 6/6] feat: always bust require cache when in watch mode BREAKING CHANGE: before only watched files were busted --- lib/cli/watch-run.js | 63 +++++++++++--------------------------------- 1 file changed, 15 insertions(+), 48 deletions(-) diff --git a/lib/cli/watch-run.js b/lib/cli/watch-run.js index f8102bfff6..78b1f8049f 100644 --- a/lib/cli/watch-run.js +++ b/lib/cli/watch-run.js @@ -2,7 +2,6 @@ const logSymbols = require('log-symbols'); const debug = require('debug')('mocha:cli:watch'); -const path = require('path'); const chokidar = require('chokidar'); const Context = require('../context'); const collectFiles = require('./collect-files'); @@ -261,8 +260,6 @@ const createRerunner = (mocha, watcher, {beforeRun} = {}) => { // running. let runner = null; - const blastFullCache = !mocha.options.parallel; - // true if a file has changed during a test run let rerunScheduled = false; @@ -272,10 +269,8 @@ const createRerunner = (mocha, watcher, {beforeRun} = {}) => { runner = mocha.run(() => { debug('finished watch run'); runner = null; - if (blastFullCache) { - setCacheBusterKey(uniqueID()); - } - blastCache(watcher, blastFullCache); + setCacheBusterKey(uniqueID()); + blastCache(); if (rerunScheduled) { rerun(); } else { @@ -312,25 +307,6 @@ const createRerunner = (mocha, watcher, {beforeRun} = {}) => { }; }; -/** - * Return the list of absolute paths watched by a chokidar watcher. - * - * @param watcher - Instance of a chokidar watcher - * @return {string[]} - List of absolute paths - * @ignore - * @private - */ -const getWatchedFiles = watcher => { - const watchedDirs = watcher.getWatched(); - return Object.keys(watchedDirs).reduce( - (acc, dir) => [ - ...acc, - ...watchedDirs[dir].map(file => path.join(dir, file)) - ], - [] - ); -}; - /** * Hide the cursor. * @ignore @@ -359,31 +335,22 @@ const eraseLine = () => { /** * Blast all of the watched files out of `require.cache` - * @param {FSWatcher} watcher - chokidar FSWatcher - * @param {boolean} blastAll * @ignore * @private */ -const blastCache = (watcher, blastAll) => { - if (blastAll) { - Object.keys(require.cache) - // Avoid deleting mocha binary - .filter( - file => - !file.includes('mocha/bin/mocha.js') && - !file.includes('mocha/lib/mocha.js') - ) - .forEach(file => { - delete require.cache[file]; - }); - debug('deleted all files from the require cache'); - } else { - const files = getWatchedFiles(watcher); - files.forEach(file => { - delete require.cache[file]; - }); - debug('deleted %d file(s) from the require cache', files.length); - } +const blastCache = () => { + const files = Object.keys(require.cache) + // Avoid deleting mocha binary + .filter( + file => + !file.includes('mocha/bin/mocha.js') && + !file.includes('mocha/lib/mocha.js') + ); + + files.forEach(file => { + delete require.cache[file]; + }); + debug('deleted %d file(s) from the require cache', files.length); }; /**