diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c8a64f57487..09ee31e3d270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## master +### Features + +* `[jest-watch]` create new package `jest-watch` to ease custom watch plugin development ([#6318](https://github.com/facebook/jest/pull/6318)) + ### Fixes * `[expect]` toMatchObject throws TypeError when a source property is null ([#6313](https://github.com/facebook/jest/pull/6313)) diff --git a/packages/jest-cli/package.json b/packages/jest-cli/package.json index 3a8b5439b955..ef2822a43801 100644 --- a/packages/jest-cli/package.json +++ b/packages/jest-cli/package.json @@ -28,6 +28,7 @@ "jest-snapshot": "^23.0.1", "jest-util": "^23.0.1", "jest-validate": "^23.0.1", + "jest-watch": "^23.0.1", "jest-worker": "^23.0.1", "micromatch": "^2.3.11", "node-notifier": "^5.2.1", diff --git a/packages/jest-cli/src/__tests__/snapshot_interactive_mode.test.js b/packages/jest-cli/src/__tests__/snapshot_interactive_mode.test.js index 26fd83792a9f..7482116fe21b 100644 --- a/packages/jest-cli/src/__tests__/snapshot_interactive_mode.test.js +++ b/packages/jest-cli/src/__tests__/snapshot_interactive_mode.test.js @@ -6,16 +6,9 @@ */ import chalk from 'chalk'; -import {KEYS} from '../constants'; +import {KEYS} from 'jest-watch'; import SnapshotInteractiveMode from '../snapshot_interactive_mode'; -jest.mock('../lib/terminal_utils', () => ({ - getTerminalWidth: () => 80, - rightPad: () => { - ''; - }, -})); - jest.mock('ansi-escapes', () => ({ clearScreen: '[MOCK - eraseDown]', cursorRestorePosition: '[MOCK - cursorRestorePosition]', @@ -86,7 +79,7 @@ describe('SnapshotInteractiveMode', () => { test('press Q or ESC triggers an abort', () => { instance.abort = jest.fn(); - instance.put(KEYS.Q); + instance.put('q'); instance.put(KEYS.ESCAPE); expect(instance.abort).toHaveBeenCalledTimes(2); }); @@ -107,12 +100,12 @@ describe('SnapshotInteractiveMode', () => { expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.S); + instance.put('s'); expect(mockCallback).toHaveBeenCalledTimes(1); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.R); + instance.put('r'); expect(instance.getSkippedNum()).toBe(0); expect(mockCallback).nthCalledWith(2, assertions[0], false); expect(mockCallback).toHaveBeenCalledTimes(2); @@ -127,12 +120,12 @@ describe('SnapshotInteractiveMode', () => { expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.S); + instance.put('s'); expect(mockCallback).toHaveBeenCalledTimes(1); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.Q); + instance.put('q'); expect(instance.getSkippedNum()).toBe(0); expect(mockCallback).nthCalledWith(2, null, false); expect(mockCallback).toHaveBeenCalledTimes(2); @@ -157,7 +150,7 @@ describe('SnapshotInteractiveMode', () => { expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.U); + instance.put('u'); expect(mockCallback).nthCalledWith(2, assertions[0], true); expect(mockCallback).toHaveBeenCalledTimes(2); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); @@ -177,18 +170,18 @@ describe('SnapshotInteractiveMode', () => { expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.S); + instance.put('s'); expect(mockCallback).nthCalledWith(2, assertions[1], false); expect(mockCallback).toHaveBeenCalledTimes(2); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.S); + instance.put('s'); expect(mockCallback).toHaveBeenCalledTimes(2); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.R); + instance.put('r'); expect(instance.getSkippedNum()).toBe(0); expect(mockCallback).nthCalledWith(3, assertions[0], false); expect(mockCallback).toHaveBeenCalledTimes(3); @@ -224,14 +217,14 @@ describe('SnapshotInteractiveMode', () => { expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.U); + instance.put('u'); expect(mockCallback).nthCalledWith(2, assertions[0], true); expect(mockCallback).nthCalledWith(3, assertions[1], false); expect(mockCallback).toHaveBeenCalledTimes(3); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.U); + instance.put('u'); expect(mockCallback).nthCalledWith(4, assertions[1], true); expect(mockCallback).toHaveBeenCalledTimes(4); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); @@ -272,19 +265,19 @@ describe('SnapshotInteractiveMode', () => { expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.U); + instance.put('u'); expect(mockCallback).nthCalledWith(2, assertions[0], true); expect(mockCallback).nthCalledWith(3, assertions[1], false); expect(mockCallback).toHaveBeenCalledTimes(3); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.S); + instance.put('s'); expect(mockCallback).toHaveBeenCalledTimes(3); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.R); + instance.put('r'); expect(instance.getSkippedNum()).toBe(0); expect(mockCallback).nthCalledWith(4, assertions[1], false); expect(mockCallback).toHaveBeenCalledTimes(4); @@ -320,19 +313,19 @@ describe('SnapshotInteractiveMode', () => { expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.S); + instance.put('s'); expect(mockCallback).nthCalledWith(2, assertions[1], false); expect(mockCallback).toHaveBeenCalledTimes(2); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.U); + instance.put('u'); expect(mockCallback).nthCalledWith(3, assertions[1], true); expect(mockCallback).toHaveBeenCalledTimes(3); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); pipe.write.mockClear(); - instance.put(KEYS.R); + instance.put('r'); expect(instance.getSkippedNum()).toBe(0); expect(mockCallback).nthCalledWith(4, assertions[0], false); expect(mockCallback).toHaveBeenCalledTimes(4); diff --git a/packages/jest-cli/src/__tests__/watch.test.js b/packages/jest-cli/src/__tests__/watch.test.js index f214d70ab058..6db65ef0ced7 100644 --- a/packages/jest-cli/src/__tests__/watch.test.js +++ b/packages/jest-cli/src/__tests__/watch.test.js @@ -10,8 +10,7 @@ import chalk from 'chalk'; import TestWatcher from '../test_watcher'; -import JestHooks from '../jest_hooks'; -import {KEYS} from '../constants'; +import {JestHook, KEYS} from 'jest-watch'; const runJestMock = jest.fn(); const watchPluginPath = `${__dirname}/__fixtures__/watch_plugin`; @@ -286,7 +285,7 @@ describe('Watch mode flows', () => { expect(pipeMockCalls.slice(determiningTestsToRun + 1)).toMatchSnapshot(); }); - it('allows WatchPlugins to hook into JestHooks', async () => { + it('allows WatchPlugins to hook into JestHook', async () => { const apply = jest.fn(); const pluginPath = `${__dirname}/__fixtures__/plugin_path_register`; jest.doMock( @@ -498,7 +497,7 @@ describe('Watch mode flows', () => { watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); runJestMock.mockReset(); - stdin.emit(KEYS.O); + stdin.emit('o'); expect(runJestMock).toBeCalled(); expect(runJestMock.mock.calls[0][0].globalConfig).toMatchObject({ @@ -512,7 +511,7 @@ describe('Watch mode flows', () => { watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); runJestMock.mockReset(); - stdin.emit(KEYS.A); + stdin.emit('a'); expect(runJestMock).toBeCalled(); expect(runJestMock.mock.calls[0][0].globalConfig).toMatchObject({ @@ -530,12 +529,12 @@ describe('Watch mode flows', () => { }); it('Pressing "t" reruns the tests in "test name pattern" mode', async () => { - const hooks = new JestHooks(); + const hooks = new JestHook(); watch(globalConfig, contexts, pipe, hasteMapInstances, stdin, hooks); runJestMock.mockReset(); - stdin.emit(KEYS.T); + stdin.emit('t'); ['t', 'e', 's', 't'].forEach(key => stdin.emit(key)); stdin.emit(KEYS.ENTER); await nextTick(); @@ -549,12 +548,12 @@ describe('Watch mode flows', () => { }); it('Pressing "p" reruns the tests in "filename pattern" mode', async () => { - const hooks = new JestHooks(); + const hooks = new JestHook(); watch(globalConfig, contexts, pipe, hasteMapInstances, stdin, hooks); runJestMock.mockReset(); - stdin.emit(KEYS.P); + stdin.emit('p'); ['f', 'i', 'l', 'e'].forEach(key => stdin.emit(key)); stdin.emit(KEYS.ENTER); await nextTick(); @@ -568,17 +567,17 @@ describe('Watch mode flows', () => { }); it('Can combine "p" and "t" filters', async () => { - const hooks = new JestHooks(); + const hooks = new JestHook(); watch(globalConfig, contexts, pipe, hasteMapInstances, stdin, hooks); runJestMock.mockReset(); - stdin.emit(KEYS.P); + stdin.emit('p'); ['f', 'i', 'l', 'e'].forEach(key => stdin.emit(key)); stdin.emit(KEYS.ENTER); await nextTick(); - stdin.emit(KEYS.T); + stdin.emit('t'); ['t', 'e', 's', 't'].forEach(key => stdin.emit(key)); stdin.emit(KEYS.ENTER); await nextTick(); @@ -592,7 +591,7 @@ describe('Watch mode flows', () => { }); it('Pressing "u" reruns the tests in "update snapshot" mode', async () => { - const hooks = new JestHooks(); + const hooks = new JestHook(); globalConfig.updateSnapshot = 'new'; @@ -601,7 +600,7 @@ describe('Watch mode flows', () => { hooks.getEmitter().onTestRunComplete({snapshot: {failure: true}}); - stdin.emit(KEYS.U); + stdin.emit('u'); await nextTick(); expect(runJestMock.mock.calls[0][0].globalConfig).toMatchObject({ @@ -610,7 +609,7 @@ describe('Watch mode flows', () => { watchAll: false, }); - stdin.emit(KEYS.A); + stdin.emit('a'); await nextTick(); // updateSnapshot is not sticky after a run. @@ -622,11 +621,11 @@ describe('Watch mode flows', () => { results = {snapshot: {failure: true}}; - stdin.emit(KEYS.A); + stdin.emit('a'); await nextTick(); runJestMock.mockReset(); - stdin.emit(KEYS.U); + stdin.emit('u'); await nextTick(); expect(runJestMock.mock.calls[0][0].globalConfig).toMatchObject({ @@ -652,8 +651,8 @@ describe('Watch mode flows', () => { const ci_watch = require('../watch').default; ci_watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); - stdin.emit(KEYS.F); - stdin.emit(KEYS.W); + stdin.emit('f'); + stdin.emit('w'); const lastWatchDisplay = pipe.write.mock.calls.reverse()[0][0]; expect(lastWatchDisplay).toMatch('Press a to run all tests.'); diff --git a/packages/jest-cli/src/__tests__/watch_filename_pattern_mode.test.js b/packages/jest-cli/src/__tests__/watch_filename_pattern_mode.test.js index 8201e6b72427..81b4d1390634 100644 --- a/packages/jest-cli/src/__tests__/watch_filename_pattern_mode.test.js +++ b/packages/jest-cli/src/__tests__/watch_filename_pattern_mode.test.js @@ -9,12 +9,10 @@ 'use strict'; import chalk from 'chalk'; -import {KEYS} from '../constants'; +import {KEYS} from 'jest-watch'; const runJestMock = jest.fn(); -let terminalWidth; - jest.mock('ansi-escapes', () => ({ clearScreen: '[MOCK - clearScreen]', cursorDown: (count = 1) => `[MOCK - cursorDown(${count})]`, @@ -79,10 +77,6 @@ jest.doMock( }, ); -jest.doMock('../lib/terminal_utils', () => ({ - getTerminalWidth: () => terminalWidth, -})); - const watch = require('../watch').default; const nextTick = () => new Promise(res => process.nextTick(res)); @@ -98,7 +92,6 @@ describe('Watch mode flows', () => { let stdin; beforeEach(() => { - terminalWidth = 80; pipe = {write: jest.fn()}; hasteMapInstances = [{on: () => {}}]; contexts = [{config: {}}]; @@ -110,7 +103,7 @@ describe('Watch mode flows', () => { watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); // Write a enter pattern mode - stdin.emit(KEYS.P); + stdin.emit('p'); expect(pipe.write).toBeCalledWith(' pattern › '); const assertPattern = hex => { @@ -146,7 +139,7 @@ describe('Watch mode flows', () => { contexts[0].config = {rootDir: ''}; watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); - stdin.emit(KEYS.P); + stdin.emit('p'); await nextTick(); ['p', '.', '*', '1', '0'] @@ -154,18 +147,18 @@ describe('Watch mode flows', () => { .concat(KEYS.ENTER) .forEach(key => stdin.emit(key)); - stdin.emit(KEYS.T); + stdin.emit('t'); await nextTick(); ['t', 'e', 's', 't'].concat(KEYS.ENTER).forEach(key => stdin.emit(key)); await nextTick(); - stdin.emit(KEYS.C); + stdin.emit('c'); await nextTick(); pipe.write.mockReset(); - stdin.emit(KEYS.P); + stdin.emit('p'); await nextTick(); expect(pipe.write.mock.calls.join('\n')).toMatchSnapshot(); diff --git a/packages/jest-cli/src/__tests__/watch_test_name_pattern_mode.test.js b/packages/jest-cli/src/__tests__/watch_test_name_pattern_mode.test.js index 376d89a4b4a0..1fd9fef0c827 100644 --- a/packages/jest-cli/src/__tests__/watch_test_name_pattern_mode.test.js +++ b/packages/jest-cli/src/__tests__/watch_test_name_pattern_mode.test.js @@ -9,12 +9,10 @@ 'use strict'; import chalk from 'chalk'; -import {KEYS} from '../constants'; +import {KEYS} from 'jest-watch'; const runJestMock = jest.fn(); -let terminalWidth; - jest.mock('ansi-escapes', () => ({ clearScreen: '[MOCK - clearScreen]', cursorDown: (count = 1) => `[MOCK - cursorDown(${count})]`, @@ -93,10 +91,6 @@ jest.doMock( }, ); -jest.doMock('../lib/terminal_utils', () => ({ - getTerminalWidth: () => terminalWidth, -})); - const watch = require('../watch').default; const globalConfig = { @@ -112,7 +106,6 @@ describe('Watch mode flows', () => { let stdin; beforeEach(() => { - terminalWidth = 80; pipe = {write: jest.fn()}; hasteMapInstances = [{on: () => {}}]; contexts = [{config: {}}]; @@ -124,7 +117,7 @@ describe('Watch mode flows', () => { watch(globalConfig, contexts, pipe, hasteMapInstances, stdin); // Write a enter pattern mode - stdin.emit(KEYS.T); + stdin.emit('t'); expect(pipe.write).toBeCalledWith(' pattern › '); const assertPattern = hex => { diff --git a/packages/jest-cli/src/constants.js b/packages/jest-cli/src/constants.js index 2ceae57dc2b2..bdf2e5efdf85 100644 --- a/packages/jest-cli/src/constants.js +++ b/packages/jest-cli/src/constants.js @@ -11,33 +11,6 @@ const isWindows = process.platform === 'win32'; export const CLEAR = isWindows ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H'; export const ARROW = ' \u203A '; -export const KEYS = { - A: 'a', - ARROW_DOWN: '\u001b[B', - ARROW_LEFT: '\u001b[D', - ARROW_RIGHT: '\u001b[C', - ARROW_UP: '\u001b[A', - BACKSPACE: isWindows - ? Buffer.from('08', 'hex').toString() - : Buffer.from('7f', 'hex').toString(), - C: 'c', - CONTROL_C: '\u0003', - CONTROL_D: '\u0004', - ENTER: '\r', - ESCAPE: '\u001b', - F: 'f', - I: 'i', - O: 'o', - P: 'p', - Q: 'q', - QUESTION_MARK: '?', - R: 'r', - S: 's', - T: 't', - U: 'u', - W: 'w', -}; - export const ICONS = { failed: isWindows ? '\u00D7' : '\u2715', pending: '\u25CB', diff --git a/packages/jest-cli/src/lib/handle_deprecation_warnings.js b/packages/jest-cli/src/lib/handle_deprecation_warnings.js index e07228b748af..1bedf32214c9 100644 --- a/packages/jest-cli/src/lib/handle_deprecation_warnings.js +++ b/packages/jest-cli/src/lib/handle_deprecation_warnings.js @@ -8,8 +8,7 @@ */ import chalk from 'chalk'; - -import {KEYS} from '../constants'; +import {KEYS} from 'jest-watch'; export default ( pipe: stream$Writable | tty$WriteStream, diff --git a/packages/jest-cli/src/plugins/quit.js b/packages/jest-cli/src/plugins/quit.js index dfcbf8db2f9a..b8fcc7b013eb 100644 --- a/packages/jest-cli/src/plugins/quit.js +++ b/packages/jest-cli/src/plugins/quit.js @@ -6,7 +6,7 @@ * * @flow */ -import BaseWatchPlugin from '../base_watch_plugin'; +import {BaseWatchPlugin} from 'jest-watch'; class QuitPlugin extends BaseWatchPlugin { isInternal: true; diff --git a/packages/jest-cli/src/plugins/test_name_pattern.js b/packages/jest-cli/src/plugins/test_name_pattern.js index 4688ffa1ff58..2b6ea866e2e4 100644 --- a/packages/jest-cli/src/plugins/test_name_pattern.js +++ b/packages/jest-cli/src/plugins/test_name_pattern.js @@ -7,10 +7,9 @@ * @flow */ import type {GlobalConfig} from 'types/Config'; -import BaseWatchPlugin from '../base_watch_plugin'; +import {BaseWatchPlugin, Prompt} from 'jest-watch'; import TestNamePatternPrompt from '../test_name_pattern_prompt'; import activeFilters from '../lib/active_filters_message'; -import Prompt from '../lib/Prompt'; class TestNamePatternPlugin extends BaseWatchPlugin { _prompt: Prompt; diff --git a/packages/jest-cli/src/plugins/test_path_pattern.js b/packages/jest-cli/src/plugins/test_path_pattern.js index 3de06b7edf55..ec5ed0390a1e 100644 --- a/packages/jest-cli/src/plugins/test_path_pattern.js +++ b/packages/jest-cli/src/plugins/test_path_pattern.js @@ -8,10 +8,9 @@ */ import type {GlobalConfig} from 'types/Config'; -import BaseWatchPlugin from '../base_watch_plugin'; +import {BaseWatchPlugin, Prompt} from 'jest-watch'; import TestPathPatternPrompt from '../test_path_pattern_prompt'; import activeFilters from '../lib/active_filters_message'; -import Prompt from '../lib/Prompt'; class TestPathPatternPlugin extends BaseWatchPlugin { _prompt: Prompt; diff --git a/packages/jest-cli/src/plugins/update_snapshots.js b/packages/jest-cli/src/plugins/update_snapshots.js index 8f8d0a40425a..3b57b4f4cf48 100644 --- a/packages/jest-cli/src/plugins/update_snapshots.js +++ b/packages/jest-cli/src/plugins/update_snapshots.js @@ -7,8 +7,8 @@ * @flow */ import type {GlobalConfig} from 'types/Config'; -import BaseWatchPlugin from '../base_watch_plugin'; -import type {JestHookSubscriber} from '../jest_hooks'; +import type {JestHookSubscriber} from 'types/JestHooks'; +import {BaseWatchPlugin} from 'jest-watch'; class UpdateSnapshotsPlugin extends BaseWatchPlugin { _hasSnapshotFailure: boolean; diff --git a/packages/jest-cli/src/plugins/update_snapshots_interactive.js b/packages/jest-cli/src/plugins/update_snapshots_interactive.js index c05da8a97465..22859fa41aad 100644 --- a/packages/jest-cli/src/plugins/update_snapshots_interactive.js +++ b/packages/jest-cli/src/plugins/update_snapshots_interactive.js @@ -6,10 +6,10 @@ * * @flow */ -import type {JestHookSubscriber} from '../jest_hooks'; +import type {JestHookSubscriber} from 'types/JestHooks'; import type {GlobalConfig} from 'types/Config'; import type {AggregatedResult, AssertionLocation} from 'types/TestResult'; -import BaseWatchPlugin from '../base_watch_plugin'; +import {BaseWatchPlugin} from 'jest-watch'; import SnapshotInteractiveMode from '../snapshot_interactive_mode'; class UpdateSnapshotInteractivePlugin extends BaseWatchPlugin { diff --git a/packages/jest-cli/src/run_jest.js b/packages/jest-cli/src/run_jest.js index 3e076f81bcfa..59a28adff6db 100644 --- a/packages/jest-cli/src/run_jest.js +++ b/packages/jest-cli/src/run_jest.js @@ -11,6 +11,7 @@ import type {Context} from 'types/Context'; import type {ChangedFilesPromise} from 'types/ChangedFiles'; import type {GlobalConfig} from 'types/Config'; import type {AggregatedResult} from 'types/TestResult'; +import type {JestHookEmitter} from 'types/JestHooks'; import type TestWatcher from './test_watcher'; import micromatch from 'micromatch'; @@ -25,7 +26,7 @@ import TestScheduler from './test_scheduler'; import TestSequencer from './test_sequencer'; import {makeEmptyAggregatedTestResult} from './test_result_helpers'; import FailedTestsCache from './failed_tests_cache'; -import JestHooks, {type JestHookEmitter} from './jest_hooks'; +import {JestHook} from 'jest-watch'; import collectNodeHandles from './get_node_handles'; const setConfig = (contexts, newConfig) => @@ -115,7 +116,7 @@ export default (async function runJest({ globalConfig, outputStream, testWatcher, - jestHooks = new JestHooks().getEmitter(), + jestHooks = new JestHook().getEmitter(), startRun, changedFilesPromise, onComplete, diff --git a/packages/jest-cli/src/snapshot_interactive_mode.js b/packages/jest-cli/src/snapshot_interactive_mode.js index a78336aa6d37..3c668209fdf0 100644 --- a/packages/jest-cli/src/snapshot_interactive_mode.js +++ b/packages/jest-cli/src/snapshot_interactive_mode.js @@ -10,10 +10,12 @@ import type {AggregatedResult, AssertionLocation} from 'types/TestResult'; -const chalk = require('chalk'); -const ansiEscapes = require('ansi-escapes'); -const {pluralize} = require('./reporters/utils'); -const {KEYS, ARROW} = require('./constants'); +import chalk from 'chalk'; +import ansiEscapes from 'ansi-escapes'; +import {KEYS} from 'jest-watch'; + +import {pluralize} from './reporters/utils'; +import {ARROW} from './constants'; export default class SnapshotInteractiveMode { _pipe: stream$Writable | tty$WriteStream; @@ -158,7 +160,7 @@ export default class SnapshotInteractiveMode { put(key: string) { switch (key) { - case KEYS.S: + case 's': if (this._skippedNum === this._testAssertions.length) break; this._skippedNum += 1; @@ -171,14 +173,14 @@ export default class SnapshotInteractiveMode { } break; - case KEYS.U: + case 'u': this._run(true); break; - case KEYS.Q: + case 'q': case KEYS.ESCAPE: this.abort(); break; - case KEYS.R: + case 'r': this.restart(); break; case KEYS.ENTER: diff --git a/packages/jest-cli/src/test_name_pattern_prompt.js b/packages/jest-cli/src/test_name_pattern_prompt.js index 25ac22983cc5..47405f252dca 100644 --- a/packages/jest-cli/src/test_name_pattern_prompt.js +++ b/packages/jest-cli/src/test_name_pattern_prompt.js @@ -8,14 +8,14 @@ */ import type {TestResult} from 'types/TestResult'; -import type {ScrollOptions} from './lib/scroll_list'; +import type {ScrollOptions} from 'types/Watch'; -import Prompt from './lib/Prompt'; import { + PatternPrompt, + Prompt, printPatternCaret, printRestoredPatternCaret, -} from './lib/pattern_mode_helpers'; -import PatternPrompt from './pattern_prompt'; +} from 'jest-watch'; export default class TestNamePatternPrompt extends PatternPrompt { _cachedTestResults: Array; diff --git a/packages/jest-cli/src/test_path_pattern_prompt.js b/packages/jest-cli/src/test_path_pattern_prompt.js index e0b532a65696..62516cf51b25 100644 --- a/packages/jest-cli/src/test_path_pattern_prompt.js +++ b/packages/jest-cli/src/test_path_pattern_prompt.js @@ -9,15 +9,15 @@ import type {Context} from 'types/Context'; import type {Test} from 'types/TestRunner'; -import type {ScrollOptions} from './lib/scroll_list'; +import type {ScrollOptions} from 'types/Watch'; import type SearchSource from './search_source'; -import Prompt from './lib/Prompt'; import { + PatternPrompt, + Prompt, printPatternCaret, printRestoredPatternCaret, -} from './lib/pattern_mode_helpers'; -import PatternPrompt from './pattern_prompt'; +} from 'jest-watch'; type SearchSources = Array<{| context: Context, diff --git a/packages/jest-cli/src/types.js b/packages/jest-cli/src/types.js index 664065b6c083..2a6c192458cc 100644 --- a/packages/jest-cli/src/types.js +++ b/packages/jest-cli/src/types.js @@ -7,7 +7,7 @@ * @flow */ import type {GlobalConfig} from 'types/Config'; -import type {JestHookSubscriber} from './jest_hooks'; +import type {JestHookSubscriber} from 'types/JestHooks'; export type UsageData = { key: string, diff --git a/packages/jest-cli/src/watch.js b/packages/jest-cli/src/watch.js index 4279625deaf1..40177fdbacf4 100644 --- a/packages/jest-cli/src/watch.js +++ b/packages/jest-cli/src/watch.js @@ -26,8 +26,8 @@ import updateGlobalConfig from './lib/update_global_config'; import SearchSource from './search_source'; import TestWatcher from './test_watcher'; import FailedTestsCache from './failed_tests_cache'; -import {KEYS, CLEAR} from './constants'; -import JestHooks from './jest_hooks'; +import {CLEAR} from './constants'; +import {KEYS, JestHook} from 'jest-watch'; import TestPathPatternPlugin from './plugins/test_path_pattern'; import TestNamePatternPlugin from './plugins/test_name_pattern'; import UpdateSnapshotsPlugin from './plugins/update_snapshots'; @@ -55,7 +55,7 @@ export default function watch( outputStream: stream$Writable | tty$WriteStream, hasteMapInstances: Array, stdin?: stream$Readable | tty$ReadStream = process.stdin, - hooks?: JestHooks = new JestHooks(), + hooks?: JestHook = new JestHook(), ): Promise { // `globalConfig` will be constantly updated and reassigned as a result of // watch mode interactions. @@ -263,9 +263,7 @@ export default function watch( if ( isRunning && testWatcher && - [KEYS.Q, KEYS.ENTER, KEYS.A, KEYS.O, KEYS.F] - .concat(pluginKeys) - .indexOf(key) !== -1 + !['q', KEYS.ENTER, 'a', 'o', 'f'].concat(pluginKeys).includes(key) ) { testWatcher.setState({interrupted: true}); return; @@ -306,7 +304,7 @@ export default function watch( case KEYS.ENTER: startRun(globalConfig); break; - case KEYS.A: + case 'a': globalConfig = updateGlobalConfig(globalConfig, { mode: 'watchAll', testNamePattern: '', @@ -314,20 +312,20 @@ export default function watch( }); startRun(globalConfig); break; - case KEYS.C: + case 'c': updateConfigAndRun({ mode: 'watch', testNamePattern: '', testPathPattern: '', }); break; - case KEYS.F: + case 'f': globalConfig = updateGlobalConfig(globalConfig, { onlyFailures: !globalConfig.onlyFailures, }); startRun(globalConfig); break; - case KEYS.O: + case 'o': globalConfig = updateGlobalConfig(globalConfig, { mode: 'watch', testNamePattern: '', @@ -335,9 +333,9 @@ export default function watch( }); startRun(globalConfig); break; - case KEYS.QUESTION_MARK: + case '?': break; - case KEYS.W: + case 'w': if (!shouldDisplayWatchUsage && !isWatchUsageDisplayed) { outputStream.write(ansiEscapes.cursorUp()); outputStream.write(ansiEscapes.eraseDown); diff --git a/packages/jest-watch/.npmignore b/packages/jest-watch/.npmignore new file mode 100644 index 000000000000..85e48fe7b0a4 --- /dev/null +++ b/packages/jest-watch/.npmignore @@ -0,0 +1,3 @@ +**/__mocks__/** +**/__tests__/** +src diff --git a/packages/jest-watch/package.json b/packages/jest-watch/package.json new file mode 100644 index 000000000000..8e11425a6feb --- /dev/null +++ b/packages/jest-watch/package.json @@ -0,0 +1,20 @@ +{ + "name": "jest-watch", + "description": "Delightful JavaScript Testing.", + "version": "23.0.1", + "main": "build/index.js", + "dependencies": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "string-length": "^2.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/facebook/jest" + }, + "bugs": { + "url": "https://github.com/facebook/jest/issues" + }, + "homepage": "http://facebook.github.io/jest/", + "license": "MIT" +} diff --git a/packages/jest-cli/src/base_watch_plugin.js b/packages/jest-watch/src/base_watch_plugin.js similarity index 94% rename from packages/jest-cli/src/base_watch_plugin.js rename to packages/jest-watch/src/base_watch_plugin.js index e19c43def1a7..b6af41c1830b 100644 --- a/packages/jest-cli/src/base_watch_plugin.js +++ b/packages/jest-watch/src/base_watch_plugin.js @@ -7,7 +7,7 @@ * @flow */ import type {GlobalConfig} from 'types/Config'; -import type {JestHookSubscriber} from './jest_hooks'; +import type {JestHookSubscriber} from 'types/JestHooks'; import type {WatchPlugin, UsageData} from './types'; class BaseWatchPlugin implements WatchPlugin { diff --git a/packages/jest-watch/src/constants.js b/packages/jest-watch/src/constants.js new file mode 100644 index 000000000000..dec73b46bf3a --- /dev/null +++ b/packages/jest-watch/src/constants.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +const isWindows = process.platform === 'win32'; + +export const KEYS = { + ARROW_DOWN: '\u001b[B', + ARROW_LEFT: '\u001b[D', + ARROW_RIGHT: '\u001b[C', + ARROW_UP: '\u001b[A', + BACKSPACE: Buffer.from(isWindows ? '08' : '7f', 'hex').toString(), + CONTROL_C: '\u0003', + CONTROL_D: '\u0004', + ENTER: '\r', + ESCAPE: '\u001b', +}; diff --git a/packages/jest-watch/src/index.js b/packages/jest-watch/src/index.js new file mode 100644 index 000000000000..fbe13be62b2c --- /dev/null +++ b/packages/jest-watch/src/index.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export {default as BaseWatchPlugin} from './base_watch_plugin'; +export {default as JestHook} from './jest_hooks'; +export {default as PatternPrompt} from './pattern_prompt'; +export * from './constants'; +export {default as Prompt} from './lib/Prompt'; +export * from './lib/pattern_mode_helpers'; diff --git a/packages/jest-cli/src/jest_hooks.js b/packages/jest-watch/src/jest_hooks.js similarity index 68% rename from packages/jest-cli/src/jest_hooks.js rename to packages/jest-watch/src/jest_hooks.js index 95eb335463fc..0d28f60ead83 100644 --- a/packages/jest-cli/src/jest_hooks.js +++ b/packages/jest-watch/src/jest_hooks.js @@ -7,33 +7,19 @@ * @flow */ -import type {AggregatedResult} from 'types/TestResult'; -import type {ProjectConfig, Path} from 'types/Config'; +import type { + JestHookSubscriber, + JestHookEmitter, + FileChange, + ShouldRunTestSuite, + TestRunComplete, +} from 'types/JestHooks'; -type JestHookExposedFS = { - projects: Array<{config: ProjectConfig, testPaths: Array}>, -}; - -type FileChange = (fs: JestHookExposedFS) => void; -type ShouldRunTestSuite = (testPath: string) => Promise; -type TestRunComplete = (results: AggregatedResult) => void; type AvailableHooks = | 'onFileChange' | 'onTestRunComplete' | 'shouldRunTestSuite'; -export type JestHookSubscriber = { - onFileChange: (fn: FileChange) => void, - onTestRunComplete: (fn: TestRunComplete) => void, - shouldRunTestSuite: (fn: ShouldRunTestSuite) => void, -}; - -export type JestHookEmitter = { - onFileChange: (fs: JestHookExposedFS) => void, - onTestRunComplete: (results: AggregatedResult) => void, - shouldRunTestSuite: (testPath: string) => Promise, -}; - class JestHooks { _listeners: { onFileChange: Array, diff --git a/packages/jest-cli/src/lib/Prompt.js b/packages/jest-watch/src/lib/Prompt.js similarity index 97% rename from packages/jest-cli/src/lib/Prompt.js rename to packages/jest-watch/src/lib/Prompt.js index cb49d3a1b585..16eebbe7fc9d 100644 --- a/packages/jest-cli/src/lib/Prompt.js +++ b/packages/jest-watch/src/lib/Prompt.js @@ -7,7 +7,7 @@ * @flow */ -import type {ScrollOptions} from './scroll_list'; +import type {ScrollOptions} from 'types/Watch'; import {KEYS} from '../constants'; diff --git a/packages/jest-cli/src/lib/__tests__/__snapshots__/format_test_name_by_pattern.test.js.snap b/packages/jest-watch/src/lib/__tests__/__snapshots__/format_test_name_by_pattern.test.js.snap similarity index 100% rename from packages/jest-cli/src/lib/__tests__/__snapshots__/format_test_name_by_pattern.test.js.snap rename to packages/jest-watch/src/lib/__tests__/__snapshots__/format_test_name_by_pattern.test.js.snap diff --git a/packages/jest-cli/src/lib/__tests__/format_test_name_by_pattern.test.js b/packages/jest-watch/src/lib/__tests__/format_test_name_by_pattern.test.js similarity index 100% rename from packages/jest-cli/src/lib/__tests__/format_test_name_by_pattern.test.js rename to packages/jest-watch/src/lib/__tests__/format_test_name_by_pattern.test.js diff --git a/packages/jest-cli/src/lib/__tests__/prompt.test.js b/packages/jest-watch/src/lib/__tests__/prompt.test.js similarity index 72% rename from packages/jest-cli/src/lib/__tests__/prompt.test.js rename to packages/jest-watch/src/lib/__tests__/prompt.test.js index 62c7a5cf9220..9416d49a7dd4 100644 --- a/packages/jest-cli/src/lib/__tests__/prompt.test.js +++ b/packages/jest-watch/src/lib/__tests__/prompt.test.js @@ -11,11 +11,6 @@ import Prompt from '../Prompt'; import {KEYS} from '../../constants'; -const EXTRA_KEYS = Object.assign({}, KEYS, { - E: 'e', - S: 's', -}); - it('calls handler on change value', () => { const options = {max: 10, offset: -1}; const prompt = new Prompt(); @@ -25,16 +20,16 @@ it('calls handler on change value', () => { expect(onChange).toHaveBeenLastCalledWith('', options); - prompt.put(EXTRA_KEYS.T); + prompt.put('t'); expect(onChange).toHaveBeenLastCalledWith('t', options); - prompt.put(EXTRA_KEYS.E); + prompt.put('e'); expect(onChange).toHaveBeenLastCalledWith('te', options); - prompt.put(EXTRA_KEYS.S); + prompt.put('s'); expect(onChange).toHaveBeenLastCalledWith('tes', options); - prompt.put(EXTRA_KEYS.T); + prompt.put('t'); expect(onChange).toHaveBeenLastCalledWith('test', options); expect(onChange).toHaveBeenCalledTimes(5); @@ -46,11 +41,11 @@ it('calls handler on success prompt', () => { prompt.enter(jest.fn(), onSuccess, jest.fn()); - prompt.put(EXTRA_KEYS.T); - prompt.put(EXTRA_KEYS.E); - prompt.put(EXTRA_KEYS.S); - prompt.put(EXTRA_KEYS.T); - prompt.put(EXTRA_KEYS.ENTER); + prompt.put('t'); + prompt.put('e'); + prompt.put('s'); + prompt.put('t'); + prompt.put(KEYS.ENTER); expect(onSuccess).toHaveBeenCalledWith('test'); }); @@ -61,11 +56,11 @@ it('calls handler on cancel prompt', () => { prompt.enter(jest.fn(), jest.fn(), onCancel); - prompt.put(EXTRA_KEYS.T); - prompt.put(EXTRA_KEYS.E); - prompt.put(EXTRA_KEYS.S); - prompt.put(EXTRA_KEYS.T); - prompt.put(EXTRA_KEYS.ESCAPE); + prompt.put('t'); + prompt.put('e'); + prompt.put('s'); + prompt.put('t'); + prompt.put(KEYS.ESCAPE); expect(onCancel).toHaveBeenCalled(); }); diff --git a/packages/jest-cli/src/lib/__tests__/scroll_list.test.js b/packages/jest-watch/src/lib/__tests__/scroll_list.test.js similarity index 100% rename from packages/jest-cli/src/lib/__tests__/scroll_list.test.js rename to packages/jest-watch/src/lib/__tests__/scroll_list.test.js diff --git a/packages/jest-cli/src/lib/colorize.js b/packages/jest-watch/src/lib/colorize.js similarity index 100% rename from packages/jest-cli/src/lib/colorize.js rename to packages/jest-watch/src/lib/colorize.js diff --git a/packages/jest-cli/src/lib/format_test_name_by_pattern.js b/packages/jest-watch/src/lib/format_test_name_by_pattern.js similarity index 100% rename from packages/jest-cli/src/lib/format_test_name_by_pattern.js rename to packages/jest-watch/src/lib/format_test_name_by_pattern.js diff --git a/packages/jest-cli/src/lib/pattern_mode_helpers.js b/packages/jest-watch/src/lib/pattern_mode_helpers.js similarity index 100% rename from packages/jest-cli/src/lib/pattern_mode_helpers.js rename to packages/jest-watch/src/lib/pattern_mode_helpers.js diff --git a/packages/jest-cli/src/lib/scroll_list.js b/packages/jest-watch/src/lib/scroll_list.js similarity index 90% rename from packages/jest-cli/src/lib/scroll_list.js rename to packages/jest-watch/src/lib/scroll_list.js index 18f51163459a..913456206139 100644 --- a/packages/jest-cli/src/lib/scroll_list.js +++ b/packages/jest-watch/src/lib/scroll_list.js @@ -9,10 +9,7 @@ 'use strict'; -export type ScrollOptions = { - offset: number, - max: number, -}; +import type {ScrollOptions} from 'types/Watch'; export default function scroll(size: number, {offset, max}: ScrollOptions) { let start = 0; diff --git a/packages/jest-cli/src/pattern_prompt.js b/packages/jest-watch/src/pattern_prompt.js similarity index 96% rename from packages/jest-cli/src/pattern_prompt.js rename to packages/jest-watch/src/pattern_prompt.js index ad3746aa7887..a25f602ca6ad 100644 --- a/packages/jest-cli/src/pattern_prompt.js +++ b/packages/jest-watch/src/pattern_prompt.js @@ -9,7 +9,7 @@ 'use strict'; -import type {ScrollOptions} from './lib/scroll_list'; +import type {ScrollOptions} from 'types/Watch'; import chalk from 'chalk'; import ansiEscapes from 'ansi-escapes'; diff --git a/packages/jest-watch/src/types.js b/packages/jest-watch/src/types.js new file mode 100644 index 000000000000..2a6c192458cc --- /dev/null +++ b/packages/jest-watch/src/types.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ +import type {GlobalConfig} from 'types/Config'; +import type {JestHookSubscriber} from 'types/JestHooks'; + +export type UsageData = { + key: string, + prompt: string, +}; + +export interface WatchPlugin { + +isInternal?: boolean; + +apply?: (hooks: JestHookSubscriber) => void; + +getUsageInfo?: (globalConfig: GlobalConfig) => ?UsageData; + +onKey?: (value: string) => void; + +run?: ( + globalConfig: GlobalConfig, + updateConfigAndRun: Function, + ) => Promise; +} diff --git a/types/JestHooks.js b/types/JestHooks.js new file mode 100644 index 000000000000..3952d9518fe3 --- /dev/null +++ b/types/JestHooks.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {AggregatedResult} from './TestResult'; +import type {Path, ProjectConfig} from './Config'; + +export type JestHookExposedFS = { + projects: Array<{config: ProjectConfig, testPaths: Array}>, +}; + +export type FileChange = (fs: JestHookExposedFS) => void; +export type ShouldRunTestSuite = (testPath: string) => Promise; +export type TestRunComplete = (results: AggregatedResult) => void; + +export type JestHookSubscriber = { + onFileChange: (fn: FileChange) => void, + onTestRunComplete: (fn: TestRunComplete) => void, + shouldRunTestSuite: (fn: ShouldRunTestSuite) => void, +}; + +export type JestHookEmitter = { + onFileChange: (fs: JestHookExposedFS) => void, + onTestRunComplete: (results: AggregatedResult) => void, + shouldRunTestSuite: (testPath: string) => Promise, +}; diff --git a/packages/jest-cli/src/lib/terminal_utils.js b/types/Watch.js similarity index 71% rename from packages/jest-cli/src/lib/terminal_utils.js rename to types/Watch.js index 70c38e3df4e4..91163d5b40c2 100644 --- a/packages/jest-cli/src/lib/terminal_utils.js +++ b/types/Watch.js @@ -7,5 +7,7 @@ * @flow */ -/* $FlowFixMe */ -export const getTerminalWidth = (): nubmer => process.stdout.columns; +export type ScrollOptions = { + offset: number, + max: number, +};