diff --git a/packages/jest-cli/src/__tests__/watch.test.js b/packages/jest-cli/src/__tests__/watch.test.js index 15ab2cb90051..1fcc111dfd7d 100644 --- a/packages/jest-cli/src/__tests__/watch.test.js +++ b/packages/jest-cli/src/__tests__/watch.test.js @@ -18,6 +18,31 @@ const watchPluginPath = `${__dirname}/__fixtures__/watch_plugin`; const watchPlugin2Path = `${__dirname}/__fixtures__/watch_plugin2`; let results; +jest.mock( + '../search_source', + () => + class { + constructor(context) { + this._context = context; + } + + findMatchingTests(pattern) { + const paths = [ + './path/to/file1-test.js', + './path/to/file2-test.js', + ].filter(path => path.match(pattern)); + + return { + tests: paths.map(path => ({ + context: this._context, + duration: null, + path, + })), + }; + } + }, +); + jest.doMock('chalk', () => new chalk.constructor({enabled: false})); jest.doMock( '../run_jest', @@ -274,6 +299,41 @@ describe('Watch mode flows', () => { expect(apply).toHaveBeenCalled(); }); + it('allows WatchPlugins to hook into file system changes', async () => { + const fileChange = jest.fn(); + const pluginPath = `${__dirname}/__fixtures__/plugin_path_fs_change`; + jest.doMock( + pluginPath, + () => + class WatchPlugin { + apply(jestHooks) { + jestHooks.fileChange(fileChange); + } + }, + {virtual: true}, + ); + + watch( + Object.assign({}, globalConfig, { + rootDir: __dirname, + watchPlugins: [pluginPath], + }), + contexts, + pipe, + hasteMapInstances, + stdin, + ); + + expect(fileChange).toHaveBeenCalledWith({ + projects: [ + { + config: contexts[0].config, + testPaths: ['./path/to/file1-test.js', './path/to/file2-test.js'], + }, + ], + }); + }); + it('triggers enter on a WatchPlugin when its key is pressed', async () => { const run = jest.fn(() => Promise.resolve()); const pluginPath = `${__dirname}/__fixtures__/plugin_path`; diff --git a/packages/jest-cli/src/jest_hooks.js b/packages/jest-cli/src/jest_hooks.js index 15b787ce00a2..eb36d708385f 100644 --- a/packages/jest-cli/src/jest_hooks.js +++ b/packages/jest-cli/src/jest_hooks.js @@ -8,35 +8,52 @@ */ import type {AggregatedResult} from 'types/TestResult'; +import type {ProjectConfig, Path} from 'types/Config'; +type JestHookExposedFS = { + projects: Array<{config: ProjectConfig, testPaths: Array}>, +}; + +type FsChange = (fs: JestHookExposedFS) => void; type ShouldRunTestSuite = (testPath: string) => Promise; type TestRunComplete = (results: AggregatedResult) => void; export type JestHookSubscriber = { + fileChange: (fn: FsChange) => void, shouldRunTestSuite: (fn: ShouldRunTestSuite) => void, testRunComplete: (fn: TestRunComplete) => void, }; export type JestHookEmitter = { + fileChange: (fs: JestHookExposedFS) => void, shouldRunTestSuite: (testPath: string) => Promise, testRunComplete: (results: AggregatedResult) => void, }; class JestHooks { _listeners: { + fileChange: Array, shouldRunTestSuite: Array, testRunComplete: Array, }; constructor() { this._listeners = { + fileChange: [], shouldRunTestSuite: [], testRunComplete: [], }; } + isUsed(hook: string) { + return this._listeners[hook] && this._listeners[hook].length; + } + getSubscriber(): JestHookSubscriber { return { + fileChange: fn => { + this._listeners.fileChange.push(fn); + }, shouldRunTestSuite: fn => { this._listeners.shouldRunTestSuite.push(fn); }, @@ -48,6 +65,8 @@ class JestHooks { getEmitter(): JestHookEmitter { return { + fileChange: fs => + this._listeners.fileChange.forEach(listener => listener(fs)), shouldRunTestSuite: async testPath => Promise.all( this._listeners.shouldRunTestSuite.map(listener => diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index b10ddb7a7219..d1ce6275c80e 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -154,6 +154,18 @@ export default function watch( let shouldDisplayWatchUsage = true; let isWatchUsageDisplayed = false; + const emitFsChange = () => { + if (hooks.isUsed('fileChange')) { + const projects = searchSources.map(({context, searchSource}) => ({ + config: context.config, + testPaths: searchSource.findMatchingTests('').tests.map(t => t.path), + })); + hooks.getEmitter().fileChange({projects}); + } + }; + + emitFsChange(); + hasteMapInstances.forEach((hasteMapInstance, index) => { hasteMapInstance.on('change', ({eventsQueue, hasteFS, moduleMap}) => { const validPaths = eventsQueue.filter(({filePath}) => { @@ -176,6 +188,7 @@ export default function watch( context, searchSource: new SearchSource(context), }; + emitFsChange(); startRun(globalConfig); } });