192 lines
4.3 KiB
JavaScript
192 lines
4.3 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const path = require('path');
|
||
|
const chokidar = require('chokidar');
|
||
|
const Context = require('../context');
|
||
|
const collectFiles = require('./collect-files');
|
||
|
|
||
|
/**
|
||
|
* Exports the `watchRun` function that runs mocha in "watch" mode.
|
||
|
* @see module:lib/cli/run-helpers
|
||
|
* @module
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Run Mocha in "watch" mode
|
||
|
* @param {Mocha} mocha - Mocha instance
|
||
|
* @param {Object} opts - Options
|
||
|
* @param {string[]} [opts.watchFiles] - List of paths and patterns to
|
||
|
* watch. If not provided all files with an extension included in
|
||
|
* `fileColletionParams.extension` are watched. See first argument of
|
||
|
* `chokidar.watch`.
|
||
|
* @param {string[]} opts.watchIgnore - List of paths and patterns to
|
||
|
* exclude from watching. See `ignored` option of `chokidar`.
|
||
|
* @param {Object} fileCollectParams - Parameters that control test
|
||
|
* file collection. See `lib/cli/collect-files.js`.
|
||
|
* @param {string[]} fileCollectParams.extension - List of extensions
|
||
|
* to watch if `opts.watchFiles` is not given.
|
||
|
* @private
|
||
|
*/
|
||
|
module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
|
||
|
if (!watchFiles) {
|
||
|
watchFiles = fileCollectParams.extension.map(ext => `**/*.${ext}`);
|
||
|
}
|
||
|
|
||
|
const watcher = chokidar.watch(watchFiles, {
|
||
|
ignored: watchIgnore,
|
||
|
ignoreInitial: true
|
||
|
});
|
||
|
|
||
|
const rerunner = createRerunner(mocha, () => {
|
||
|
getWatchedFiles(watcher).forEach(file => {
|
||
|
delete require.cache[file];
|
||
|
});
|
||
|
mocha.files = collectFiles(fileCollectParams);
|
||
|
});
|
||
|
|
||
|
watcher.on('ready', () => {
|
||
|
rerunner.run();
|
||
|
});
|
||
|
|
||
|
watcher.on('all', () => {
|
||
|
rerunner.scheduleRun();
|
||
|
});
|
||
|
|
||
|
console.log();
|
||
|
hideCursor();
|
||
|
process.on('exit', () => {
|
||
|
showCursor();
|
||
|
});
|
||
|
process.on('SIGINT', () => {
|
||
|
showCursor();
|
||
|
console.log('\n');
|
||
|
process.exit(128 + 2);
|
||
|
});
|
||
|
|
||
|
// Keyboard shortcut for restarting when "rs\n" is typed (ala Nodemon)
|
||
|
process.stdin.resume();
|
||
|
process.stdin.setEncoding('utf8');
|
||
|
process.stdin.on('data', data => {
|
||
|
const str = data
|
||
|
.toString()
|
||
|
.trim()
|
||
|
.toLowerCase();
|
||
|
if (str === 'rs') rerunner.scheduleRun();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Create an object that allows you to rerun tests on the mocha
|
||
|
* instance. `beforeRun` is called everytime before `mocha.run()` is
|
||
|
* called.
|
||
|
*
|
||
|
* @param {Mocha} mocha - Mocha instance
|
||
|
* @param {function} beforeRun - Called just before `mocha.run()`
|
||
|
*/
|
||
|
const createRerunner = (mocha, beforeRun) => {
|
||
|
// Set to a `Runner` when mocha is running. Set to `null` when mocha is not
|
||
|
// running.
|
||
|
let runner = null;
|
||
|
|
||
|
let rerunScheduled = false;
|
||
|
|
||
|
const run = () => {
|
||
|
try {
|
||
|
beforeRun();
|
||
|
resetMocha(mocha);
|
||
|
runner = mocha.run(() => {
|
||
|
runner = null;
|
||
|
if (rerunScheduled) {
|
||
|
rerun();
|
||
|
}
|
||
|
});
|
||
|
} catch (e) {
|
||
|
console.log(e.stack);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const scheduleRun = () => {
|
||
|
if (rerunScheduled) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
rerunScheduled = true;
|
||
|
if (runner) {
|
||
|
runner.abort();
|
||
|
} else {
|
||
|
rerun();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const rerun = () => {
|
||
|
rerunScheduled = false;
|
||
|
eraseLine();
|
||
|
run();
|
||
|
};
|
||
|
|
||
|
return {
|
||
|
scheduleRun,
|
||
|
run
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Return the list of absolute paths watched by a chokidar watcher.
|
||
|
*
|
||
|
* @param watcher - Instance of a chokidar watcher
|
||
|
* @return {string[]} - List of absolute paths
|
||
|
*/
|
||
|
const getWatchedFiles = watcher => {
|
||
|
const watchedDirs = watcher.getWatched();
|
||
|
let watchedFiles = [];
|
||
|
Object.keys(watchedDirs).forEach(dir => {
|
||
|
watchedFiles = watchedFiles.concat(
|
||
|
watchedDirs[dir].map(file => path.join(dir, file))
|
||
|
);
|
||
|
});
|
||
|
return watchedFiles;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Reset the internal state of the mocha instance so that tests can be rerun.
|
||
|
*
|
||
|
* @param {Mocha} mocha - Mocha instance
|
||
|
* @private
|
||
|
*/
|
||
|
const resetMocha = mocha => {
|
||
|
mocha.unloadFiles();
|
||
|
mocha.suite = mocha.suite.clone();
|
||
|
mocha.suite.ctx = new Context();
|
||
|
// Registers a callback on `mocha.suite` that wires new context to the DSL
|
||
|
// (e.g. `describe`) that is exposed as globals when the test files are
|
||
|
// reloaded.
|
||
|
mocha.ui(mocha.options.ui);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Hide the cursor.
|
||
|
* @ignore
|
||
|
* @private
|
||
|
*/
|
||
|
const hideCursor = () => {
|
||
|
process.stdout.write('\u001b[?25l');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Show the cursor.
|
||
|
* @ignore
|
||
|
* @private
|
||
|
*/
|
||
|
const showCursor = () => {
|
||
|
process.stdout.write('\u001b[?25h');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Erases the line on stdout
|
||
|
* @private
|
||
|
*/
|
||
|
const eraseLine = () => {
|
||
|
process.stdout.write('\u001b[2K');
|
||
|
};
|