UNPKG

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