UNPKG

19.7 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.default = watch;
7function path() {
8 const data = _interopRequireWildcard(require('path'));
9 path = function () {
10 return data;
11 };
12 return data;
13}
14function _ansiEscapes() {
15 const data = _interopRequireDefault(require('ansi-escapes'));
16 _ansiEscapes = function () {
17 return data;
18 };
19 return data;
20}
21function _chalk() {
22 const data = _interopRequireDefault(require('chalk'));
23 _chalk = function () {
24 return data;
25 };
26 return data;
27}
28function _exit() {
29 const data = _interopRequireDefault(require('exit'));
30 _exit = function () {
31 return data;
32 };
33 return data;
34}
35function _slash() {
36 const data = _interopRequireDefault(require('slash'));
37 _slash = function () {
38 return data;
39 };
40 return data;
41}
42function _jestMessageUtil() {
43 const data = require('jest-message-util');
44 _jestMessageUtil = function () {
45 return data;
46 };
47 return data;
48}
49function _jestUtil() {
50 const data = require('jest-util');
51 _jestUtil = function () {
52 return data;
53 };
54 return data;
55}
56function _jestValidate() {
57 const data = require('jest-validate');
58 _jestValidate = function () {
59 return data;
60 };
61 return data;
62}
63function _jestWatcher() {
64 const data = require('jest-watcher');
65 _jestWatcher = function () {
66 return data;
67 };
68 return data;
69}
70var _FailedTestsCache = _interopRequireDefault(require('./FailedTestsCache'));
71var _SearchSource = _interopRequireDefault(require('./SearchSource'));
72var _getChangedFilesPromise = _interopRequireDefault(
73 require('./getChangedFilesPromise')
74);
75var _activeFiltersMessage = _interopRequireDefault(
76 require('./lib/activeFiltersMessage')
77);
78var _createContext = _interopRequireDefault(require('./lib/createContext'));
79var _isValidPath = _interopRequireDefault(require('./lib/isValidPath'));
80var _updateGlobalConfig = _interopRequireDefault(
81 require('./lib/updateGlobalConfig')
82);
83var _watchPluginsHelpers = require('./lib/watchPluginsHelpers');
84var _FailedTestsInteractive = _interopRequireDefault(
85 require('./plugins/FailedTestsInteractive')
86);
87var _Quit = _interopRequireDefault(require('./plugins/Quit'));
88var _TestNamePattern = _interopRequireDefault(
89 require('./plugins/TestNamePattern')
90);
91var _TestPathPattern = _interopRequireDefault(
92 require('./plugins/TestPathPattern')
93);
94var _UpdateSnapshots = _interopRequireDefault(
95 require('./plugins/UpdateSnapshots')
96);
97var _UpdateSnapshotsInteractive = _interopRequireDefault(
98 require('./plugins/UpdateSnapshotsInteractive')
99);
100var _runJest = _interopRequireDefault(require('./runJest'));
101function _interopRequireDefault(obj) {
102 return obj && obj.__esModule ? obj : {default: obj};
103}
104function _getRequireWildcardCache(nodeInterop) {
105 if (typeof WeakMap !== 'function') return null;
106 var cacheBabelInterop = new WeakMap();
107 var cacheNodeInterop = new WeakMap();
108 return (_getRequireWildcardCache = function (nodeInterop) {
109 return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
110 })(nodeInterop);
111}
112function _interopRequireWildcard(obj, nodeInterop) {
113 if (!nodeInterop && obj && obj.__esModule) {
114 return obj;
115 }
116 if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
117 return {default: obj};
118 }
119 var cache = _getRequireWildcardCache(nodeInterop);
120 if (cache && cache.has(obj)) {
121 return cache.get(obj);
122 }
123 var newObj = {};
124 var hasPropertyDescriptor =
125 Object.defineProperty && Object.getOwnPropertyDescriptor;
126 for (var key in obj) {
127 if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
128 var desc = hasPropertyDescriptor
129 ? Object.getOwnPropertyDescriptor(obj, key)
130 : null;
131 if (desc && (desc.get || desc.set)) {
132 Object.defineProperty(newObj, key, desc);
133 } else {
134 newObj[key] = obj[key];
135 }
136 }
137 }
138 newObj.default = obj;
139 if (cache) {
140 cache.set(obj, newObj);
141 }
142 return newObj;
143}
144/**
145 * Copyright (c) Meta Platforms, Inc. and affiliates.
146 *
147 * This source code is licensed under the MIT license found in the
148 * LICENSE file in the root directory of this source tree.
149 */
150
151const {print: preRunMessagePrint} = _jestUtil().preRunMessage;
152let hasExitListener = false;
153const INTERNAL_PLUGINS = [
154 _FailedTestsInteractive.default,
155 _TestPathPattern.default,
156 _TestNamePattern.default,
157 _UpdateSnapshots.default,
158 _UpdateSnapshotsInteractive.default,
159 _Quit.default
160];
161const RESERVED_KEY_PLUGINS = new Map([
162 [
163 _UpdateSnapshots.default,
164 {
165 forbiddenOverwriteMessage: 'updating snapshots',
166 key: 'u'
167 }
168 ],
169 [
170 _UpdateSnapshotsInteractive.default,
171 {
172 forbiddenOverwriteMessage: 'updating snapshots interactively',
173 key: 'i'
174 }
175 ],
176 [
177 _Quit.default,
178 {
179 forbiddenOverwriteMessage: 'quitting watch mode'
180 }
181 ]
182]);
183async function watch(
184 initialGlobalConfig,
185 contexts,
186 outputStream,
187 hasteMapInstances,
188 stdin = process.stdin,
189 hooks = new (_jestWatcher().JestHook)(),
190 filter
191) {
192 // `globalConfig` will be constantly updated and reassigned as a result of
193 // watch mode interactions.
194 let globalConfig = initialGlobalConfig;
195 let activePlugin;
196 globalConfig = (0, _updateGlobalConfig.default)(globalConfig, {
197 mode: globalConfig.watch ? 'watch' : 'watchAll',
198 passWithNoTests: true
199 });
200 const updateConfigAndRun = ({
201 bail,
202 changedSince,
203 collectCoverage,
204 collectCoverageFrom,
205 coverageDirectory,
206 coverageReporters,
207 findRelatedTests,
208 mode,
209 nonFlagArgs,
210 notify,
211 notifyMode,
212 onlyFailures,
213 reporters,
214 testNamePattern,
215 testPathPattern,
216 updateSnapshot,
217 verbose
218 } = {}) => {
219 const previousUpdateSnapshot = globalConfig.updateSnapshot;
220 globalConfig = (0, _updateGlobalConfig.default)(globalConfig, {
221 bail,
222 changedSince,
223 collectCoverage,
224 collectCoverageFrom,
225 coverageDirectory,
226 coverageReporters,
227 findRelatedTests,
228 mode,
229 nonFlagArgs,
230 notify,
231 notifyMode,
232 onlyFailures,
233 reporters,
234 testNamePattern,
235 testPathPattern,
236 updateSnapshot,
237 verbose
238 });
239 startRun(globalConfig);
240 globalConfig = (0, _updateGlobalConfig.default)(globalConfig, {
241 // updateSnapshot is not sticky after a run.
242 updateSnapshot:
243 previousUpdateSnapshot === 'all' ? 'none' : previousUpdateSnapshot
244 });
245 };
246 const watchPlugins = INTERNAL_PLUGINS.map(
247 InternalPlugin =>
248 new InternalPlugin({
249 stdin,
250 stdout: outputStream
251 })
252 );
253 watchPlugins.forEach(plugin => {
254 const hookSubscriber = hooks.getSubscriber();
255 if (plugin.apply) {
256 plugin.apply(hookSubscriber);
257 }
258 });
259 if (globalConfig.watchPlugins != null) {
260 const watchPluginKeys = new Map();
261 for (const plugin of watchPlugins) {
262 const reservedInfo = RESERVED_KEY_PLUGINS.get(plugin.constructor) || {};
263 const key = reservedInfo.key || getPluginKey(plugin, globalConfig);
264 if (!key) {
265 continue;
266 }
267 const {forbiddenOverwriteMessage} = reservedInfo;
268 watchPluginKeys.set(key, {
269 forbiddenOverwriteMessage,
270 overwritable: forbiddenOverwriteMessage == null,
271 plugin
272 });
273 }
274 for (const pluginWithConfig of globalConfig.watchPlugins) {
275 let plugin;
276 try {
277 const ThirdPartyPlugin = await (0, _jestUtil().requireOrImportModule)(
278 pluginWithConfig.path
279 );
280 plugin = new ThirdPartyPlugin({
281 config: pluginWithConfig.config,
282 stdin,
283 stdout: outputStream
284 });
285 } catch (error) {
286 const errorWithContext = new Error(
287 `Failed to initialize watch plugin "${_chalk().default.bold(
288 (0, _slash().default)(
289 path().relative(process.cwd(), pluginWithConfig.path)
290 )
291 )}":\n\n${(0, _jestMessageUtil().formatExecError)(
292 error,
293 contexts[0].config,
294 {
295 noStackTrace: false
296 }
297 )}`
298 );
299 delete errorWithContext.stack;
300 return Promise.reject(errorWithContext);
301 }
302 checkForConflicts(watchPluginKeys, plugin, globalConfig);
303 const hookSubscriber = hooks.getSubscriber();
304 if (plugin.apply) {
305 plugin.apply(hookSubscriber);
306 }
307 watchPlugins.push(plugin);
308 }
309 }
310 const failedTestsCache = new _FailedTestsCache.default();
311 let searchSources = contexts.map(context => ({
312 context,
313 searchSource: new _SearchSource.default(context)
314 }));
315 let isRunning = false;
316 let testWatcher;
317 let shouldDisplayWatchUsage = true;
318 let isWatchUsageDisplayed = false;
319 const emitFileChange = () => {
320 if (hooks.isUsed('onFileChange')) {
321 const projects = searchSources.map(({context, searchSource}) => ({
322 config: context.config,
323 testPaths: searchSource.findMatchingTests('').tests.map(t => t.path)
324 }));
325 hooks.getEmitter().onFileChange({
326 projects
327 });
328 }
329 };
330 emitFileChange();
331 hasteMapInstances.forEach((hasteMapInstance, index) => {
332 hasteMapInstance.on('change', ({eventsQueue, hasteFS, moduleMap}) => {
333 const validPaths = eventsQueue.filter(({filePath}) =>
334 (0, _isValidPath.default)(globalConfig, filePath)
335 );
336 if (validPaths.length) {
337 const context = (contexts[index] = (0, _createContext.default)(
338 contexts[index].config,
339 {
340 hasteFS,
341 moduleMap
342 }
343 ));
344 activePlugin = null;
345 searchSources = searchSources.slice();
346 searchSources[index] = {
347 context,
348 searchSource: new _SearchSource.default(context)
349 };
350 emitFileChange();
351 startRun(globalConfig);
352 }
353 });
354 });
355 if (!hasExitListener) {
356 hasExitListener = true;
357 process.on('exit', () => {
358 if (activePlugin) {
359 outputStream.write(_ansiEscapes().default.cursorDown());
360 outputStream.write(_ansiEscapes().default.eraseDown);
361 }
362 });
363 }
364 const startRun = globalConfig => {
365 if (isRunning) {
366 return Promise.resolve(null);
367 }
368 testWatcher = new (_jestWatcher().TestWatcher)({
369 isWatchMode: true
370 });
371 _jestUtil().isInteractive &&
372 outputStream.write(_jestUtil().specialChars.CLEAR);
373 preRunMessagePrint(outputStream);
374 isRunning = true;
375 const configs = contexts.map(context => context.config);
376 const changedFilesPromise = (0, _getChangedFilesPromise.default)(
377 globalConfig,
378 configs
379 );
380 return (0, _runJest.default)({
381 changedFilesPromise,
382 contexts,
383 failedTestsCache,
384 filter,
385 globalConfig,
386 jestHooks: hooks.getEmitter(),
387 onComplete: results => {
388 isRunning = false;
389 hooks.getEmitter().onTestRunComplete(results);
390
391 // Create a new testWatcher instance so that re-runs won't be blocked.
392 // The old instance that was passed to Jest will still be interrupted
393 // and prevent test runs from the previous run.
394 testWatcher = new (_jestWatcher().TestWatcher)({
395 isWatchMode: true
396 });
397
398 // Do not show any Watch Usage related stuff when running in a
399 // non-interactive environment
400 if (_jestUtil().isInteractive) {
401 if (shouldDisplayWatchUsage) {
402 outputStream.write(usage(globalConfig, watchPlugins));
403 shouldDisplayWatchUsage = false; // hide Watch Usage after first run
404 isWatchUsageDisplayed = true;
405 } else {
406 outputStream.write(showToggleUsagePrompt());
407 shouldDisplayWatchUsage = false;
408 isWatchUsageDisplayed = false;
409 }
410 } else {
411 outputStream.write('\n');
412 }
413 failedTestsCache.setTestResults(results.testResults);
414 },
415 outputStream,
416 startRun,
417 testWatcher
418 }).catch(error =>
419 // Errors thrown inside `runJest`, e.g. by resolvers, are caught here for
420 // continuous watch mode execution. We need to reprint them to the
421 // terminal and give just a little bit of extra space so they fit below
422 // `preRunMessagePrint` message nicely.
423 console.error(
424 `\n\n${(0, _jestMessageUtil().formatExecError)(
425 error,
426 contexts[0].config,
427 {
428 noStackTrace: false
429 }
430 )}`
431 )
432 );
433 };
434 const onKeypress = key => {
435 if (
436 key === _jestWatcher().KEYS.CONTROL_C ||
437 key === _jestWatcher().KEYS.CONTROL_D
438 ) {
439 if (typeof stdin.setRawMode === 'function') {
440 stdin.setRawMode(false);
441 }
442 outputStream.write('\n');
443 (0, _exit().default)(0);
444 return;
445 }
446 if (activePlugin != null && activePlugin.onKey) {
447 // if a plugin is activate, Jest should let it handle keystrokes, so ignore
448 // them here
449 activePlugin.onKey(key);
450 return;
451 }
452
453 // Abort test run
454 const pluginKeys = (0, _watchPluginsHelpers.getSortedUsageRows)(
455 watchPlugins,
456 globalConfig
457 ).map(usage => Number(usage.key).toString(16));
458 if (
459 isRunning &&
460 testWatcher &&
461 ['q', _jestWatcher().KEYS.ENTER, 'a', 'o', 'f']
462 .concat(pluginKeys)
463 .includes(key)
464 ) {
465 testWatcher.setState({
466 interrupted: true
467 });
468 return;
469 }
470 const matchingWatchPlugin = (0,
471 _watchPluginsHelpers.filterInteractivePlugins)(
472 watchPlugins,
473 globalConfig
474 ).find(plugin => getPluginKey(plugin, globalConfig) === key);
475 if (matchingWatchPlugin != null) {
476 if (isRunning) {
477 testWatcher.setState({
478 interrupted: true
479 });
480 return;
481 }
482 // "activate" the plugin, which has jest ignore keystrokes so the plugin
483 // can handle them
484 activePlugin = matchingWatchPlugin;
485 if (activePlugin.run) {
486 activePlugin.run(globalConfig, updateConfigAndRun).then(
487 shouldRerun => {
488 activePlugin = null;
489 if (shouldRerun) {
490 updateConfigAndRun();
491 }
492 },
493 () => {
494 activePlugin = null;
495 onCancelPatternPrompt();
496 }
497 );
498 } else {
499 activePlugin = null;
500 }
501 }
502 switch (key) {
503 case _jestWatcher().KEYS.ENTER:
504 startRun(globalConfig);
505 break;
506 case 'a':
507 globalConfig = (0, _updateGlobalConfig.default)(globalConfig, {
508 mode: 'watchAll',
509 testNamePattern: '',
510 testPathPattern: ''
511 });
512 startRun(globalConfig);
513 break;
514 case 'c':
515 updateConfigAndRun({
516 mode: 'watch',
517 testNamePattern: '',
518 testPathPattern: ''
519 });
520 break;
521 case 'f':
522 globalConfig = (0, _updateGlobalConfig.default)(globalConfig, {
523 onlyFailures: !globalConfig.onlyFailures
524 });
525 startRun(globalConfig);
526 break;
527 case 'o':
528 globalConfig = (0, _updateGlobalConfig.default)(globalConfig, {
529 mode: 'watch',
530 testNamePattern: '',
531 testPathPattern: ''
532 });
533 startRun(globalConfig);
534 break;
535 case '?':
536 break;
537 case 'w':
538 if (!shouldDisplayWatchUsage && !isWatchUsageDisplayed) {
539 outputStream.write(_ansiEscapes().default.cursorUp());
540 outputStream.write(_ansiEscapes().default.eraseDown);
541 outputStream.write(usage(globalConfig, watchPlugins));
542 isWatchUsageDisplayed = true;
543 shouldDisplayWatchUsage = false;
544 }
545 break;
546 }
547 };
548 const onCancelPatternPrompt = () => {
549 outputStream.write(_ansiEscapes().default.cursorHide);
550 outputStream.write(_jestUtil().specialChars.CLEAR);
551 outputStream.write(usage(globalConfig, watchPlugins));
552 outputStream.write(_ansiEscapes().default.cursorShow);
553 };
554 if (typeof stdin.setRawMode === 'function') {
555 stdin.setRawMode(true);
556 stdin.resume();
557 stdin.setEncoding('utf8');
558 stdin.on('data', onKeypress);
559 }
560 startRun(globalConfig);
561 return Promise.resolve();
562}
563const checkForConflicts = (watchPluginKeys, plugin, globalConfig) => {
564 const key = getPluginKey(plugin, globalConfig);
565 if (!key) {
566 return;
567 }
568 const conflictor = watchPluginKeys.get(key);
569 if (!conflictor || conflictor.overwritable) {
570 watchPluginKeys.set(key, {
571 overwritable: false,
572 plugin
573 });
574 return;
575 }
576 let error;
577 if (conflictor.forbiddenOverwriteMessage) {
578 error = `
579 Watch plugin ${_chalk().default.bold.red(
580 getPluginIdentifier(plugin)
581 )} attempted to register key ${_chalk().default.bold.red(`<${key}>`)},
582 that is reserved internally for ${_chalk().default.bold.red(
583 conflictor.forbiddenOverwriteMessage
584 )}.
585 Please change the configuration key for this plugin.`.trim();
586 } else {
587 const plugins = [conflictor.plugin, plugin]
588 .map(p => _chalk().default.bold.red(getPluginIdentifier(p)))
589 .join(' and ');
590 error = `
591 Watch plugins ${plugins} both attempted to register key ${_chalk().default.bold.red(
592 `<${key}>`
593 )}.
594 Please change the key configuration for one of the conflicting plugins to avoid overlap.`.trim();
595 }
596 throw new (_jestValidate().ValidationError)(
597 'Watch plugin configuration error',
598 error
599 );
600};
601const getPluginIdentifier = plugin =>
602 // This breaks as `displayName` is not defined as a static, but since
603 // WatchPlugin is an interface, and it is my understanding interface
604 // static fields are not definable anymore, no idea how to circumvent
605 // this :-(
606 // @ts-expect-error: leave `displayName` be.
607 plugin.constructor.displayName || plugin.constructor.name;
608const getPluginKey = (plugin, globalConfig) => {
609 if (typeof plugin.getUsageInfo === 'function') {
610 return (
611 plugin.getUsageInfo(globalConfig) || {
612 key: null
613 }
614 ).key;
615 }
616 return null;
617};
618const usage = (globalConfig, watchPlugins, delimiter = '\n') => {
619 const messages = [
620 (0, _activeFiltersMessage.default)(globalConfig),
621 globalConfig.testPathPattern || globalConfig.testNamePattern
622 ? `${_chalk().default.dim(' \u203A Press ')}c${_chalk().default.dim(
623 ' to clear filters.'
624 )}`
625 : null,
626 `\n${_chalk().default.bold('Watch Usage')}`,
627 globalConfig.watch
628 ? `${_chalk().default.dim(' \u203A Press ')}a${_chalk().default.dim(
629 ' to run all tests.'
630 )}`
631 : null,
632 globalConfig.onlyFailures
633 ? `${_chalk().default.dim(' \u203A Press ')}f${_chalk().default.dim(
634 ' to quit "only failed tests" mode.'
635 )}`
636 : `${_chalk().default.dim(' \u203A Press ')}f${_chalk().default.dim(
637 ' to run only failed tests.'
638 )}`,
639 (globalConfig.watchAll ||
640 globalConfig.testPathPattern ||
641 globalConfig.testNamePattern) &&
642 !globalConfig.noSCM
643 ? `${_chalk().default.dim(' \u203A Press ')}o${_chalk().default.dim(
644 ' to only run tests related to changed files.'
645 )}`
646 : null,
647 ...(0, _watchPluginsHelpers.getSortedUsageRows)(
648 watchPlugins,
649 globalConfig
650 ).map(
651 plugin =>
652 `${_chalk().default.dim(' \u203A Press')} ${
653 plugin.key
654 } ${_chalk().default.dim(`to ${plugin.prompt}.`)}`
655 ),
656 `${_chalk().default.dim(' \u203A Press ')}Enter${_chalk().default.dim(
657 ' to trigger a test run.'
658 )}`
659 ];
660 return `${messages.filter(message => !!message).join(delimiter)}\n`;
661};
662const showToggleUsagePrompt = () =>
663 '\n' +
664 `${_chalk().default.bold('Watch Usage: ')}${_chalk().default.dim(
665 'Press '
666 )}w${_chalk().default.dim(' to show more.')}`;