Skip to content

Commit

Permalink
Refactor pattern prompts
Browse files Browse the repository at this point in the history
  • Loading branch information
thymikee committed May 4, 2017
1 parent ffc8b94 commit 4ff8918
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 314 deletions.
61 changes: 61 additions & 0 deletions packages/jest-cli/src/PatternPrompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

import type {ScrollOptions} from './lib/scrollList';

const chalk = require('chalk');
const ansiEscapes = require('ansi-escapes');
const Prompt = require('./lib/Prompt');

const usage = (entity: string) =>
`\n${chalk.bold('Pattern Mode Usage')}\n` +
` ${chalk.dim('\u203A Press')} Esc ${chalk.dim('to exit pattern mode.')}\n` +
` ${chalk.dim('\u203A Press')} Enter ` +
`${chalk.dim(`to apply pattern to all ${entity}.`)}\n` +
`\n`;

const usageRows = usage('').split('\n').length;

module.exports = class PatternPrompt {
_pipe: stream$Writable | tty$WriteStream;
_prompt: Prompt;
_entityName: string;
_currentUsageRows: number;

constructor(pipe: stream$Writable | tty$WriteStream, prompt: Prompt) {
this._pipe = pipe;
this._prompt = prompt;
this._currentUsageRows = usageRows;
}

run(onSuccess: Function, onCancel: Function, options?: {header: string}) {
this._pipe.write(ansiEscapes.cursorHide);
this._pipe.write(ansiEscapes.clearScreen);

if (options && options.header) {
this._pipe.write(options.header + '\n');
this._currentUsageRows = usageRows + options.header.split('\n').length;
} else {
this._currentUsageRows = usageRows;
}

this._pipe.write(usage(this._entityName));
this._pipe.write(ansiEscapes.cursorShow);

this._prompt.enter(this._onChange.bind(this), onSuccess, onCancel);
}

_onChange(pattern: string, options: ScrollOptions) {
this._pipe.write(ansiEscapes.eraseLine);
this._pipe.write(ansiEscapes.cursorLeft);
}
};
109 changes: 30 additions & 79 deletions packages/jest-cli/src/TestNamePatternPrompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,114 +13,65 @@
import type {TestResult} from 'types/TestResult';
import type {ScrollOptions} from './lib/scrollList';

const ansiEscapes = require('ansi-escapes');
const chalk = require('chalk');
const scroll = require('./lib/scrollList');
const {getTerminalWidth} = require('./lib/terminalUtils');
const stringLength = require('string-length');
const Prompt = require('./lib/Prompt');
const formatTestNameByPattern = require('./lib/formatTestNameByPattern');

const pluralizeTest = (total: number) => (total === 1 ? 'test' : 'tests');

const usage = () =>
`\n${chalk.bold('Pattern Mode Usage')}\n` +
` ${chalk.dim('\u203A Press')} Esc ${chalk.dim('to exit pattern mode.')}\n` +
` ${chalk.dim('\u203A Press')} Enter ` +
`${chalk.dim('to apply pattern to all tests.')}\n` +
`\n`;

const usageRows = usage().split('\n').length;

module.exports = class TestNamePatternPrompt {
const {
formatTypeaheadSelection,
printMore,
printPatternCaret,
printPatternMatches,
printRestoredPatternCaret,
printStartTyping,
printTypeaheadItem,
} = require('./lib/patternModeHelpers');
const PatternPrompt = require('./PatternPrompt');

module.exports = class TestNamePatternPrompt extends PatternPrompt {
_cachedTestResults: Array<TestResult>;
_pipe: stream$Writable | tty$WriteStream;
_prompt: Prompt;
_currentUsageRows: number;

constructor(pipe: stream$Writable | tty$WriteStream, prompt: Prompt) {
this._pipe = pipe;
this._prompt = prompt;
this._currentUsageRows = usageRows;
}

run(onSuccess: Function, onCancel: Function, options?: {header: string}) {
this._pipe.write(ansiEscapes.cursorHide);
this._pipe.write(ansiEscapes.clearScreen);
if (options && options.header) {
this._pipe.write(options.header + '\n');
this._currentUsageRows = usageRows + options.header.split('\n').length;
} else {
this._currentUsageRows = usageRows;
}
this._pipe.write(usage());
this._pipe.write(ansiEscapes.cursorShow);

this._prompt.enter(this._onChange.bind(this), onSuccess, onCancel);
super(pipe, prompt);
this._entityName = 'tests';
}

_onChange(pattern: string, options: ScrollOptions) {
this._pipe.write(ansiEscapes.eraseLine);
this._pipe.write(ansiEscapes.cursorLeft);
super._onChange(pattern, options);
this._printTypeahead(pattern, options);
}

_printTypeahead(pattern: string, options: ScrollOptions) {
const { max } = options;
const {max} = options;
const matchedTests = this._getMatchedTests(pattern);

const total = matchedTests.length;
const inputText = `${chalk.dim(' pattern \u203A')} ${pattern}`;
const pipe = this._pipe;
const prompt = this._prompt;

this._pipe.write(ansiEscapes.eraseDown);
this._pipe.write(inputText);
this._pipe.write(ansiEscapes.cursorSavePosition);
printPatternCaret(pattern, pipe);

if (pattern) {
if (total) {
this._pipe.write(
`\n\n Pattern matches ${total} ${pluralizeTest(total)}`,
);
} else {
this._pipe.write(`\n\n Pattern matches no tests`);
}

this._pipe.write(' from cached test suites.');
printPatternMatches(total, 'test', pipe, ` from cached test suites`);

const width = getTerminalWidth();
const { start, end, index } = scroll(total, options);
const {start, end, index} = scroll(total, options);

this._prompt.setTypeaheadLength(total);
prompt.setTypeaheadLength(total);

matchedTests.slice(start, end)
.map(name => formatTestNameByPattern(name, pattern, width - 4))
.map((item, i) => {
if (i === index) {
this._prompt.setTypheadheadSelection(chalk.stripColor(item));
return chalk.black.bgYellow(chalk.stripColor(item));
}
return item;
})
.forEach(output => this._pipe.write(`\n ${chalk.dim('\u203A')} ${output}`));
matchedTests
.slice(start, end)
.map(name => formatTestNameByPattern(name, pattern, width - 4))
.map((item, i) => formatTypeaheadSelection(item, i, index, prompt))
.forEach(item => printTypeaheadItem(item, pipe));

if (total > max) {
const more = total - max;
this._pipe.write(
// eslint-disable-next-line max-len
`\n ${chalk.dim(`\u203A and ${more} more ${pluralizeTest(more)}`)}`,
);
printMore('test', pipe, total - max);
}
} else {
this._pipe.write(
// eslint-disable-next-line max-len
`\n\n ${chalk.italic.yellow('Start typing to filter by a test name regex pattern.')}`,
);
printStartTyping('test name', pipe);
}

this._pipe.write(
ansiEscapes.cursorTo(stringLength(inputText), this._currentUsageRows - 1),
);
this._pipe.write(ansiEscapes.cursorRestorePosition);
printRestoredPatternCaret(pattern, this._currentUsageRows, pipe);
}

_getMatchedTests(pattern: string) {
Expand Down
142 changes: 50 additions & 92 deletions packages/jest-cli/src/TestPathPatternPrompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,104 +15,63 @@ import type {Test} from 'types/TestRunner';
import type {ScrollOptions} from './lib/scrollList';
import type SearchSource from './SearchSource';

const ansiEscapes = require('ansi-escapes');
const chalk = require('chalk');
const scroll = require('./lib/scrollList');
const {getTerminalWidth} = require('./lib/terminalUtils');
const highlight = require('./lib/highlight');
const stringLength = require('string-length');
const {trimAndFormatPath} = require('./reporters/utils');
const Prompt = require('./lib/Prompt');
const {
formatTypeaheadSelection,
printMore,
printPatternCaret,
printPatternMatches,
printRestoredPatternCaret,
printStartTyping,
printTypeaheadItem,
} = require('./lib/patternModeHelpers');
const PatternPrompt = require('./PatternPrompt');

type SearchSources = Array<{|
context: Context,
searchSource: SearchSource,
|}>;

const pluralizeFile = (total: number) => (total === 1 ? 'file' : 'files');

const usage = () =>
`\n${chalk.bold('Pattern Mode Usage')}\n` +
` ${chalk.dim('\u203A Press')} Esc ${chalk.dim('to exit pattern mode.')}\n` +
` ${chalk.dim('\u203A Press')} Enter ` +
`${chalk.dim('to apply pattern to all filenames.')}\n` +
`\n`;

const usageRows = usage().split('\n').length;

module.exports = class TestPathPatternPrompt {
_pipe: stream$Writable | tty$WriteStream;
_prompt: Prompt;
module.exports = class TestPathPatternPrompt extends PatternPrompt {
_searchSources: SearchSources;
_currentUsageRows: number;

constructor(pipe: stream$Writable | tty$WriteStream, prompt: Prompt) {
this._pipe = pipe;
this._prompt = prompt;
this._currentUsageRows = usageRows;
}

run(onSuccess: Function, onCancel: Function, options?: {header: string}) {
this._pipe.write(ansiEscapes.cursorHide);
this._pipe.write(ansiEscapes.clearScreen);
if (options && options.header) {
this._pipe.write(options.header + '\n');
this._currentUsageRows = usageRows + options.header.split('\n').length;
} else {
this._currentUsageRows = usageRows;
}
this._pipe.write(usage());
this._pipe.write(ansiEscapes.cursorShow);

this._prompt.enter(this._onChange.bind(this), onSuccess, onCancel);
super(pipe, prompt);
this._entityName = 'filenames';
}

_onChange(pattern: string, options: ScrollOptions) {
let regex;

try {
regex = new RegExp(pattern, 'i');
} catch (e) {}

let tests = [];
if (regex) {
this._searchSources.forEach(({searchSource, context}) => {
tests = tests.concat(searchSource.findMatchingTests(pattern).tests);
});
}

this._pipe.write(ansiEscapes.eraseLine);
this._pipe.write(ansiEscapes.cursorLeft);
this._printTypeahead(pattern, tests, options);
super._onChange(pattern, options);
this._printTypeahead(pattern, options);
}

_printTypeahead(pattern: string, allResults: Array<Test>, options: ScrollOptions) {
const { max } = options;
const total = allResults.length;
const results = allResults.slice(0, max);
const inputText = `${chalk.dim(' pattern \u203A')} ${pattern}`;
_printTypeahead(pattern: string, options: ScrollOptions) {
const {max} = options;
const matchedTests = this._getMatchedTests(pattern);
const total = matchedTests.length;
const pipe = this._pipe;
const prompt = this._prompt;

this._pipe.write(ansiEscapes.eraseDown);
this._pipe.write(inputText);
this._pipe.write(ansiEscapes.cursorSavePosition);
printPatternCaret(pattern, pipe);

if (pattern) {
if (total) {
this._pipe.write(
`\n\n Pattern matches ${total} ${pluralizeFile(total)}.`,
);
} else {
this._pipe.write(`\n\n Pattern matches no files.`);
}
printPatternMatches(total, 'file', pipe);

const width = getTerminalWidth();
const prefix = ` ${chalk.dim('\u203A')} `;
const padding = stringLength(prefix) + 2;
const width = getTerminalWidth();
const {start, end, index} = scroll(total, options);

const { start, end, index } = scroll(total, options);
prompt.setTypeaheadLength(total);

this._prompt.setTypeaheadLength(total);
allResults.slice(start, end)
matchedTests
.slice(start, end)
.map(({path, context}) => {
const filePath = trimAndFormatPath(
padding,
Expand All @@ -122,35 +81,34 @@ module.exports = class TestPathPatternPrompt {
);
return highlight(path, filePath, pattern, context.config.rootDir);
})
.map((item, i) => {
if (i === index) {
this._prompt.setTypheadheadSelection(chalk.stripColor(item));
return chalk.black.bgYellow(chalk.stripColor(item));
}
return item;
})
.forEach(filePath =>
this._pipe.write(`\n ${chalk.dim('\u203A')} ${filePath}`),
);
.map((item, i) => formatTypeaheadSelection(item, i, index, prompt))
.forEach(item => printTypeaheadItem(item, pipe));

if (total > max) {
const more = total - max;
this._pipe.write(
// eslint-disable-next-line max-len
`\n ${chalk.dim(`\u203A and ${more} more ${pluralizeFile(more)}`)}`,
);
printMore('file', pipe, total - max);
}
} else {
this._pipe.write(
// eslint-disable-next-line max-len
`\n\n ${chalk.italic.yellow('Start typing to filter by a filename regex pattern.')}`,
);
printStartTyping('filename', pipe);
}

printRestoredPatternCaret(pattern, this._currentUsageRows, pipe);
}

_getMatchedTests(pattern: string): Array<Test> {
let regex;

try {
regex = new RegExp(pattern, 'i');
} catch (e) {}

let tests = [];
if (regex) {
this._searchSources.forEach(({searchSource, context}) => {
tests = tests.concat(searchSource.findMatchingTests(pattern).tests);
});
}

this._pipe.write(
ansiEscapes.cursorTo(stringLength(inputText), this._currentUsageRows - 1),
);
this._pipe.write(ansiEscapes.cursorRestorePosition);
return tests;
}

updateSearchSources(searchSources: SearchSources) {
Expand Down
Loading

0 comments on commit 4ff8918

Please sign in to comment.