Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add arrows selection to typeahead #3386

Merged
merged 11 commits into from
May 4, 2017
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);
}
};
121 changes: 44 additions & 77 deletions packages/jest-cli/src/TestNamePatternPrompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,106 +11,73 @@
'use strict';

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;
super(pipe, prompt);
this._entityName = 'tests';
this._cachedTestResults = [];
}

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);
_onChange(pattern: string, options: ScrollOptions) {
super._onChange(pattern, options);
this._printTypeahead(pattern, options);
}

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

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

const total = matchedTests.length;
const results = matchedTests.slice(0, max);
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 ${require('chalk').yellow('cached')} test suites`,
);

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

results.forEach(name => {
const testName = formatTestNameByPattern(name, pattern, width - 4);
prompt.setTypeaheadLength(total);

this._pipe.write(`\n ${chalk.dim('\u203A')} ${testName}`);
});
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)}`)}`,
);
if (total > end) {
printMore('test', pipe, total - end);
}
} 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 All @@ -135,7 +102,7 @@ module.exports = class TestNamePatternPrompt {
return matchedTests;
}

updateCachedTestResults(testResults: Array<TestResult>) {
this._cachedTestResults = testResults || [];
updateCachedTestResults(testResults: Array<TestResult> = []) {
this._cachedTestResults = testResults;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for this change?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just like initial values better :)

}
};
Loading