Jest’s watch mode is surprisingly flexible, and you can actually hijack its interactive prompts to build custom tools that feel like part of Jest itself.
Let’s see what this looks like. Imagine you want a plugin that, after tests run, asks you if you want to see a detailed report of which tests failed, or just a summary.
First, we need a way to hook into Jest’s watch mode. This is done via the watchPlugins option in your jest.config.js. You’ll point this to a module that exports a apply function.
// jest.config.js
module.exports = {
// ... other Jest config
watchPlugins: [
'jest-watch-typeahead', // Example of a built-in plugin
require.resolve('./my-custom-watch-plugin.js'),
],
};
Now, for the plugin itself. The apply function receives a jest instance, which is our gateway to Jest’s internal watch mode utilities.
// my-custom-watch-plugin.js
const chalk = require('chalk');
module.exports = function(jestHooks) {
jestHooks.onFileChange(({ matches }) => {
// This hook runs when files change and Jest is in watch mode.
// We can use this to trigger our custom prompt logic.
if (matches.tests.length > 0) {
promptForReportType();
}
});
jestHooks.onTestResult(({ results }) => {
// This hook runs after tests have been executed.
// We can use this to decide *if* we should prompt.
if (results.numFailed > 0) {
promptForReportType();
}
});
function promptForReportType() {
// This is where we'll use Jest's prompt utility.
// We'll need to get access to it.
}
};
The key to interacting with the user is Jest’s jest.cli.prompt function. It’s a bit of an internal API, but it’s the standard way to get interactive input within Jest’s watch mode. You can access it via the jest instance passed to your plugin’s apply function.
// my-custom-watch-plugin.js
const chalk = require('chalk');
module.exports = function(jestHooks, jestConfig) {
const prompt = jestHooks.getPrompt(); // Get the prompt utility
jestHooks.onTestResult(({ results }) => {
if (results.numFailed > 0) {
promptForReportType(prompt, results);
}
});
function promptForReportType(prompt, results) {
prompt({
type: 'select',
name: 'reportType',
message: chalk.yellow('Tests failed. What kind of report do you want?'),
choices: [
{ title: 'Detailed Failures', value: 'detailed' },
{ title: 'Summary Only', value: 'summary' },
],
}).then((answer) => {
if (answer.reportType === 'detailed') {
console.log(chalk.red('\n--- Detailed Failure Report ---'));
results.testResults.forEach(testSuite => {
testSuite.assertionResults.forEach(assertion => {
if (assertion.status === 'failed') {
console.log(chalk.red(`\nTest Suite: ${testSuite.name}`));
console.log(chalk.red(` Failure: ${assertion.title}`));
console.log(chalk.red(` Message: ${assertion.failureMessages.join('\n ')}`));
}
});
});
console.log(chalk.red('-----------------------------\n'));
} else {
console.log(chalk.yellow('\n--- Summary ---'));
console.log(`Total Tests: ${results.numTotalTestSuites}`);
console.log(`Passed: ${results.numPassedTests}`);
console.log(`Failed: ${results.numFailedTests}`);
console.log('---------------\n');
}
});
}
};
The prompt utility is essentially a wrapper around prompts.js, providing a familiar interface for common interactive prompts like text input, selection, and confirmation. By using jestHooks.getPrompt(), you ensure you’re using Jest’s own, potentially customized, prompting mechanism.
When you run Jest in watch mode (jest --watch), and a test fails, you’ll now see your custom prompt appear in the terminal. Selecting "Detailed Failures" will iterate through the results object and print out the specific assertion failures, while "Summary Only" will provide a high-level overview.
This pattern of hooking into onFileChange and onTestResult and then using jestHooks.getPrompt() is the foundation for building all sorts of interactive tooling. You could, for instance, create a plugin that prompts you to run specific test suites, or one that offers to generate a code coverage report after a full test run. The key is understanding that Jest’s watch mode isn’t just for re-running tests; it’s a programmable environment.
The most surprising part is how deeply you can integrate. Jest doesn’t just let you add prompts; it exposes its internal state and event bus, allowing your plugin to react to test outcomes and file changes with the same granularity as Jest’s core watch logic. This means you can not only ask questions but also act based on the answers, modifying Jest’s subsequent behavior or triggering external scripts, all within the watch mode lifecycle.
What you’re seeing here is just the tip of the iceberg. The jest object passed to apply also gives you access to jest.runCLI, which can be used to trigger new test runs with different configurations, or jest.getProjectForConfig, which is useful if you have a multi-project Jest setup.