diff --git a/CHANGELOG.md b/CHANGELOG.md index 39021d60caf7..6b3354026a67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[jest-cli]` Allow watch plugin to be configured ([#6603](https://github.com/facebook/jest/pull/6603)) - `[jest-snapshot]` Introduce `toMatchInlineSnapshot` and `toThrowErrorMatchingInlineSnapshot` matchers ([#6380](https://github.com/facebook/jest/pull/6380)) ### Chore & Maintenance diff --git a/docs/WatchPlugins.md b/docs/WatchPlugins.md index 8536bda66afa..7e731cdb53fc 100644 --- a/docs/WatchPlugins.md +++ b/docs/WatchPlugins.md @@ -148,3 +148,36 @@ class MyWatchPlugin { } } ``` + +## Customization + +Plugins can be customized via your Jest configuration. + +```javascript +// jest.config.js +module.exports = { + // ... + watchPlugins: [ + [ + 'path/to/yourWatchPlugin', + { + key: 'k', // <- your custom key + prompt: 'show a custom prompt', + }, + ], + ], +}; +``` + +Recommended config names: + +- `key`: Modifies the plugin key. +- `prompt`: Allows user to customize the text in the plugin prompt. + +If the user provided a custom configuration, it will be passed as an argument to the plugin constructor. + +```javascript +class MyWatchPlugin { + constructor({config}) {} +} +``` diff --git a/packages/jest-cli/src/__tests__/__snapshots__/watch.test.js.snap b/packages/jest-cli/src/__tests__/__snapshots__/watch.test.js.snap index 38f4a2d573ad..88ce3867a970 100644 --- a/packages/jest-cli/src/__tests__/__snapshots__/watch.test.js.snap +++ b/packages/jest-cli/src/__tests__/__snapshots__/watch.test.js.snap @@ -21,6 +21,21 @@ Watch Usage ] `; +exports[`Watch mode flows allows WatchPlugins to be configured 1`] = ` +Array [ + " +Watch Usage + › Press a to run all tests. + › Press f to run only failed tests. + › Press p to filter by a filename regex pattern. + › Press t to filter by a test name regex pattern. + › Press q to quit watch mode. + › Press k to filter with a custom prompt. + › Press Enter to trigger a test run. +", +] +`; + exports[`Watch mode flows allows WatchPlugins to override internal plugins 1`] = ` Array [ " diff --git a/packages/jest-cli/src/__tests__/watch.test.js b/packages/jest-cli/src/__tests__/watch.test.js index 7b374b2f07b5..0107973cbd31 100644 --- a/packages/jest-cli/src/__tests__/watch.test.js +++ b/packages/jest-cli/src/__tests__/watch.test.js @@ -176,7 +176,7 @@ describe('Watch mode flows', () => { await watch( Object.assign({}, globalConfig, { rootDir: __dirname, - watchPlugins: [watchPluginPath], + watchPlugins: [{config: {}, path: watchPluginPath}], }), contexts, pipe, @@ -195,7 +195,10 @@ describe('Watch mode flows', () => { ci_watch( Object.assign({}, globalConfig, { rootDir: __dirname, - watchPlugins: [watchPlugin2Path, watchPluginPath], + watchPlugins: [ + {config: {}, path: watchPluginPath}, + {config: {}, path: watchPlugin2Path}, + ], }), contexts, pipe, @@ -302,7 +305,7 @@ describe('Watch mode flows', () => { watch( Object.assign({}, globalConfig, { rootDir: __dirname, - watchPlugins: [pluginPath], + watchPlugins: [{config: {}, path: pluginPath}], }), contexts, pipe, @@ -338,7 +341,7 @@ describe('Watch mode flows', () => { watch( Object.assign({}, globalConfig, { rootDir: __dirname, - watchPlugins: [pluginPath], + watchPlugins: [{config: {}, path: pluginPath}], }), contexts, pipe, @@ -356,6 +359,47 @@ describe('Watch mode flows', () => { expect(run).toHaveBeenCalled(); }); + it('allows WatchPlugins to be configured', async () => { + const pluginPath = `${__dirname}/__fixtures__/plugin_path_with_config`; + jest.doMock( + pluginPath, + () => + class WatchPlugin { + constructor({config}) { + this._key = config.key; + this._prompt = config.prompt; + } + onKey() {} + run() {} + getUsageInfo() { + return { + key: this._key || 'z', + prompt: this._prompt || 'default prompt', + }; + } + }, + {virtual: true}, + ); + + watch( + Object.assign({}, globalConfig, { + rootDir: __dirname, + watchPlugins: [ + { + config: {key: 'k', prompt: 'filter with a custom prompt'}, + path: pluginPath, + }, + ], + }), + contexts, + pipe, + hasteMapInstances, + stdin, + ); + + expect(pipe.write.mock.calls.reverse()[0]).toMatchSnapshot(); + }); + it('allows WatchPlugins to hook into file system changes', async () => { const onFileChange = jest.fn(); const pluginPath = `${__dirname}/__fixtures__/plugin_path_fs_change`; @@ -373,7 +417,7 @@ describe('Watch mode flows', () => { watch( Object.assign({}, globalConfig, { rootDir: __dirname, - watchPlugins: [pluginPath], + watchPlugins: [{config: {}, path: pluginPath}], }), contexts, pipe, @@ -414,7 +458,7 @@ describe('Watch mode flows', () => { watch( Object.assign({}, globalConfig, { rootDir: __dirname, - watchPlugins: [pluginPath], + watchPlugins: [{config: {}, path: pluginPath}], }), contexts, pipe, @@ -474,7 +518,10 @@ describe('Watch mode flows', () => { watch( Object.assign({}, globalConfig, { rootDir: __dirname, - watchPlugins: [pluginPath, pluginPath2], + watchPlugins: [ + {config: {}, path: pluginPath}, + {config: {}, path: pluginPath2}, + ], }), contexts, pipe, diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index e6ae3d1cb081..0a0c71f32d0b 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -106,7 +106,6 @@ export default function watch( const watchPlugins: Array = INTERNAL_PLUGINS.map( InternalPlugin => new InternalPlugin({stdin, stdout: outputStream}), ); - watchPlugins.forEach((plugin: WatchPlugin) => { const hookSubscriber = hooks.getSubscriber(); if (plugin.apply) { @@ -115,10 +114,11 @@ export default function watch( }); if (globalConfig.watchPlugins != null) { - for (const pluginModulePath of globalConfig.watchPlugins) { + for (const pluginWithConfig of globalConfig.watchPlugins) { // $FlowFixMe dynamic require - const ThirdPartyPlugin = require(pluginModulePath); + const ThirdPartyPlugin = require(pluginWithConfig.path); const plugin: WatchPlugin = new ThirdPartyPlugin({ + config: pluginWithConfig.config, stdin, stdout: outputStream, }); diff --git a/packages/jest-config/src/__tests__/normalize.test.js b/packages/jest-config/src/__tests__/normalize.test.js index bc2b5a759231..a1f0f610ec4c 100644 --- a/packages/jest-config/src/__tests__/normalize.test.js +++ b/packages/jest-config/src/__tests__/normalize.test.js @@ -1127,8 +1127,8 @@ describe('watchPlugins', () => { ); expect(options.watchPlugins).toEqual([ - '/node_modules/my-watch-plugin', - '/root/path/to/plugin', + {config: {}, path: '/node_modules/my-watch-plugin'}, + {config: {}, path: '/root/path/to/plugin'}, ]); }); }); diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.js index 769874db3c48..6863022e0870 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.js @@ -579,13 +579,27 @@ export default function normalize(options: InitialOptions, argv: Argv) { value = options[key]; break; case 'watchPlugins': - value = (options[key] || []).map(watchPlugin => - resolve(newOptions.resolver, { - filePath: watchPlugin, - key, - rootDir: options.rootDir, - }), - ); + value = (options[key] || []).map(watchPlugin => { + if (typeof watchPlugin === 'string') { + return { + config: {}, + path: resolve(newOptions.resolver, { + filePath: watchPlugin, + key, + rootDir: options.rootDir, + }), + }; + } else { + return { + config: watchPlugin[1] || {}, + path: resolve(newOptions.resolver, { + filePath: watchPlugin[0], + key, + rootDir: options.rootDir, + }), + }; + } + }); break; } newOptions[key] = value; diff --git a/types/Config.js b/types/Config.js index 29c1bf891188..4d4abf994e6f 100644 --- a/types/Config.js +++ b/types/Config.js @@ -178,7 +178,7 @@ export type InitialOptions = { watch?: boolean, watchAll?: boolean, watchman?: boolean, - watchPlugins?: Array, + watchPlugins?: Array, }; export type SnapshotUpdateState = 'all' | 'new' | 'none'; @@ -235,7 +235,7 @@ export type GlobalConfig = {| watch: boolean, watchAll: boolean, watchman: boolean, - watchPlugins: ?Array, + watchPlugins: ?Array<{path: string, config: Object}>, |}; export type ProjectConfig = {|