From 0dacd1fb0067e40f8567653f828f677022e4fb89 Mon Sep 17 00:00:00 2001 From: "P. Roebuck" Date: Thu, 14 Feb 2019 14:18:12 -0600 Subject: [PATCH] Add ability to unload files from `require` cache (redux) (#3726) * Implemented `Mocha.unloadFile` and `Mocha#unloadFiles`. * Added feature tests, as well as ones for `Mocha#addFiles` and `Mocha#loadFiles`. * Beefed up some unrelated "mocha.js" tests, adding missing chainability tests. --- lib/cli/run-helpers.js | 11 +++--- lib/mocha.js | 42 +++++++++++++++++++-- test/node-unit/mocha.spec.js | 72 ++++++++++++++++++++++++++++++++++++ test/unit/mocha.spec.js | 32 ++++++++++++---- 4 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 test/node-unit/mocha.spec.js diff --git a/lib/cli/run-helpers.js b/lib/cli/run-helpers.js index d953a06909..89832083ed 100644 --- a/lib/cli/run-helpers.js +++ b/lib/cli/run-helpers.js @@ -8,12 +8,13 @@ */ const fs = require('fs'); +const path = require('path'); +const ansi = require('ansi-colors'); const debug = require('debug')('mocha:cli:run:helpers'); +const minimatch = require('minimatch'); const Context = require('../context'); -const path = require('path'); +const Mocha = require('../mocha'); const utils = require('../utils'); -const minimatch = require('minimatch'); -const ansi = require('ansi-colors'); const cwd = (exports.cwd = process.cwd()); @@ -249,9 +250,7 @@ exports.watchRun = ( }; const purge = () => { - watchFiles.forEach(file => { - delete require.cache[file]; - }); + watchFiles.forEach(Mocha.unloadFile); }; loadAndRun(); diff --git a/lib/mocha.js b/lib/mocha.js index 1537196167..9707f4c66d 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -301,7 +301,6 @@ Mocha.prototype.ui = function(name) { }; /** - * @summary * Loads `files` prior to execution. * * @description @@ -310,6 +309,8 @@ Mocha.prototype.ui = function(name) { * * @private * @see {@link Mocha#addFile} + * @see {@link Mocha#run} + * @see {@link Mocha#unloadFiles} * @param {Function} [fn] - Callback invoked upon completion. */ Mocha.prototype.loadFiles = function(fn) { @@ -324,6 +325,39 @@ Mocha.prototype.loadFiles = function(fn) { fn && fn(); }; +/** + * Removes a previously loaded file from Node's `require` cache. + * + * @private + * @static + * @see {@link Mocha#unloadFiles} + * @param {string} file - Pathname of file to be unloaded. + */ +Mocha.unloadFile = function(file) { + delete require.cache[require.resolve(file)]; +}; + +/** + * Unloads `files` from Node's `require` cache. + * + * @description + * This allows files to be "freshly" reloaded, providing the ability + * to reuse a Mocha instance programmatically. + * + * Intended for consumers — not used internally + * + * @public + * @see {@link Mocha.unloadFile} + * @see {@link Mocha#loadFiles} + * @see {@link Mocha#run} + * @returns {Mocha} this + * @chainable + */ +Mocha.prototype.unloadFiles = function() { + this.files.forEach(Mocha.unloadFile); + return this; +}; + /** * Sets `grep` filter after escaping RegExp special characters. * @@ -741,8 +775,7 @@ Object.defineProperty(Mocha.prototype, 'version', { */ /** - * @summary - * Runs tests and invokes `fn()` when complete. + * Runs root suite and invokes `fn()` when complete. * * @description * To run tests multiple times (or to run tests in files that are @@ -751,6 +784,7 @@ Object.defineProperty(Mocha.prototype, 'version', { * * @public * @see {@link Mocha#loadFiles} + * @see {@link Mocha#unloadFiles} * @see {@link Runner#run} * @param {DoneCB} [fn] - Callback invoked when test execution completed. * @return {Runner} runner instance @@ -787,7 +821,7 @@ Mocha.prototype.run = function(fn) { exports.reporters.Base.hideDiff = options.hideDiff; function done(failures) { - fn = fn || function fn() {}; + fn = fn || utils.noop; if (reporter.done) { reporter.done(failures, fn); } else { diff --git a/test/node-unit/mocha.spec.js b/test/node-unit/mocha.spec.js new file mode 100644 index 0000000000..314a012023 --- /dev/null +++ b/test/node-unit/mocha.spec.js @@ -0,0 +1,72 @@ +'use strict'; + +const path = require('path'); +const Mocha = require('../../lib/mocha'); +const utils = require('../../lib/utils'); + +describe('Mocha', function() { + const opts = {reporter: utils.noop}; // no output + const testFiles = [ + __filename, + path.join(__dirname, 'cli', 'config.spec.js'), + path.join(__dirname, 'cli', 'run.spec.js') + ]; + const resolvedTestFiles = testFiles.map(require.resolve); + + describe('#addFile', function() { + it('should add the given file to the files array', function() { + const mocha = new Mocha(opts); + mocha.addFile(__filename); + expect(mocha.files, 'to have length', 1).and('to contain', __filename); + }); + + it('should be chainable', function() { + const mocha = new Mocha(opts); + expect(mocha.addFile(__filename), 'to be', mocha); + }); + }); + + describe('#loadFiles', function() { + it('should load all files from the files array', function() { + const mocha = new Mocha(opts); + + testFiles.forEach(mocha.addFile, mocha); + mocha.loadFiles(); + expect(require.cache, 'to have keys', resolvedTestFiles); + }); + + it('should execute the optional callback if given', function() { + const mocha = new Mocha(opts); + expect(cb => { + mocha.loadFiles(cb); + }, 'to call the callback'); + }); + }); + + describe('.unloadFile', function() { + it('should unload a specific file from cache', function() { + const resolvedFilePath = require.resolve(__filename); + require(__filename); + expect(require.cache, 'to have key', resolvedFilePath); + + Mocha.unloadFile(__filename); + expect(require.cache, 'not to have key', resolvedFilePath); + }); + }); + + describe('#unloadFiles', function() { + it('should unload all test files from cache', function() { + const mocha = new Mocha(opts); + + testFiles.forEach(mocha.addFile, mocha); + mocha.loadFiles(); + mocha.unloadFiles(); + expect(require.cache, 'not to have keys', resolvedTestFiles); + }); + + it('should be chainable', function() { + const mocha = new Mocha(opts); + expect(mocha.unloadFiles(), 'to be', mocha); + }); + }); +}); diff --git a/test/unit/mocha.spec.js b/test/unit/mocha.spec.js index beb5e2b5e0..73c74f6b8b 100644 --- a/test/unit/mocha.spec.js +++ b/test/unit/mocha.spec.js @@ -21,6 +21,7 @@ describe('Mocha', function() { sandbox.stub(Mocha.prototype, 'useColors').returnsThis(); sandbox.stub(utils, 'deprecate'); }); + it('should prefer "color" over "useColors"', function() { // eslint-disable-next-line no-new new Mocha({useColors: true, color: false}); @@ -70,14 +71,6 @@ describe('Mocha', function() { }); }); - describe('.addFile()', function() { - it('should add the given file to the files array', function() { - var mocha = new Mocha(opts); - mocha.addFile('myFile.js'); - expect(mocha.files, 'to have length', 1).and('to contain', 'myFile.js'); - }); - }); - describe('.invert()', function() { it('should set the invert option to true', function() { var mocha = new Mocha(opts); @@ -153,6 +146,7 @@ describe('Mocha', function() { expect(mocha.options, 'to have property', 'growl', true); }); }); + describe('if not capable of notifications', function() { it('should set the growl option to false', function() { var mocha = new Mocha(opts); @@ -163,6 +157,7 @@ describe('Mocha', function() { expect(mocha.options, 'to have property', 'growl', false); }); }); + it('should be chainable', function() { var mocha = new Mocha(opts); expect(mocha.growl(), 'to be', mocha); @@ -195,11 +190,17 @@ describe('Mocha', function() { }); describe('.noHighlighting()', function() { + // :NOTE: Browser-only option... it('should set the noHighlighting option to true', function() { var mocha = new Mocha(opts); mocha.noHighlighting(); expect(mocha.options, 'to have property', 'noHighlighting', true); }); + + it('should be chainable', function() { + var mocha = new Mocha(opts); + expect(mocha.noHighlighting(), 'to be', mocha); + }); }); describe('.allowUncaught()', function() { @@ -208,6 +209,11 @@ describe('Mocha', function() { mocha.allowUncaught(); expect(mocha.options, 'to have property', 'allowUncaught', true); }); + + it('should be chainable', function() { + var mocha = new Mocha(opts); + expect(mocha.allowUncaught(), 'to be', mocha); + }); }); describe('.delay()', function() { @@ -216,6 +222,11 @@ describe('Mocha', function() { mocha.delay(); expect(mocha.options, 'to have property', 'delay', true); }); + + it('should be chainable', function() { + var mocha = new Mocha(opts); + expect(mocha.delay(), 'to be', mocha); + }); }); describe('.bail()', function() { @@ -224,6 +235,11 @@ describe('Mocha', function() { mocha.bail(); expect(mocha.suite._bail, 'to be', true); }); + + it('should be chainable', function() { + var mocha = new Mocha(opts); + expect(mocha.bail(), 'to be', mocha); + }); }); describe('error handling', function() {