jest-watch-typeahead 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +5 -4
- package/build/file_name_plugin/plugin.js +0 -49
- package/build/file_name_plugin/prompt.js +0 -92
- package/build/index.js +0 -8
- package/build/lib/pattern_mode_helpers.js +0 -27
- package/build/lib/scroll.js +0 -26
- package/build/lib/utils.js +0 -126
- package/build/test_name_plugin/plugin.js +0 -49
- package/build/test_name_plugin/prompt.js +0 -86
- package/build/types/Config.js +0 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "jest-watch-typeahead",
|
3
|
-
"version": "2.
|
3
|
+
"version": "2.1.0",
|
4
4
|
"main": "build/index.js",
|
5
5
|
"exports": {
|
6
6
|
".": "./build/index.js",
|
@@ -63,7 +63,7 @@
|
|
63
63
|
"eslint-plugin-jest": "^26.0.0",
|
64
64
|
"eslint-plugin-prettier": "^4.0.0",
|
65
65
|
"jest": "^28.0.0",
|
66
|
-
"jest-serializer-ansi-escapes": "^
|
66
|
+
"jest-serializer-ansi-escapes": "^2.0.1",
|
67
67
|
"prettier": "^2.1.1",
|
68
68
|
"rimraf": "^3.0.2",
|
69
69
|
"semantic-release": "^19.0.3",
|
@@ -71,7 +71,7 @@
|
|
71
71
|
"typescript": "^4.0.2"
|
72
72
|
},
|
73
73
|
"peerDependencies": {
|
74
|
-
"jest": "^27.0.0 || ^28.0.0"
|
74
|
+
"jest": "^27.0.0 || ^28.0.0 || ^29.0.0"
|
75
75
|
},
|
76
76
|
"jest": {
|
77
77
|
"extensionsToTreatAsEsm": [
|
@@ -108,5 +108,6 @@
|
|
108
108
|
"@semantic-release/git",
|
109
109
|
"@semantic-release/github"
|
110
110
|
]
|
111
|
-
}
|
111
|
+
},
|
112
|
+
"packageManager": "yarn@3.2.3"
|
112
113
|
}
|
@@ -1,49 +0,0 @@
|
|
1
|
-
import { Prompt } from 'jest-watcher';
|
2
|
-
import FileNamePatternPrompt from "./prompt.js";
|
3
|
-
export default class FileNamePlugin {
|
4
|
-
constructor({
|
5
|
-
stdin,
|
6
|
-
stdout,
|
7
|
-
config = {}
|
8
|
-
}) {
|
9
|
-
this._stdin = stdin;
|
10
|
-
this._stdout = stdout;
|
11
|
-
this._prompt = new Prompt();
|
12
|
-
this._projects = [];
|
13
|
-
this._usageInfo = {
|
14
|
-
key: config.key || 'p',
|
15
|
-
prompt: config.prompt || 'filter by a filename regex pattern'
|
16
|
-
};
|
17
|
-
}
|
18
|
-
|
19
|
-
apply(jestHooks) {
|
20
|
-
jestHooks.onFileChange(({
|
21
|
-
projects
|
22
|
-
}) => {
|
23
|
-
this._projects = projects;
|
24
|
-
});
|
25
|
-
}
|
26
|
-
|
27
|
-
onKey(key) {
|
28
|
-
this._prompt.put(key);
|
29
|
-
}
|
30
|
-
|
31
|
-
run(globalConfig, updateConfigAndRun) {
|
32
|
-
const p = new FileNamePatternPrompt(this._stdout, this._prompt);
|
33
|
-
p.updateSearchSources(this._projects);
|
34
|
-
return new Promise((res, rej) => {
|
35
|
-
p.run(testPathPattern => {
|
36
|
-
updateConfigAndRun({
|
37
|
-
mode: 'watch',
|
38
|
-
testPathPattern
|
39
|
-
});
|
40
|
-
res();
|
41
|
-
}, rej);
|
42
|
-
});
|
43
|
-
}
|
44
|
-
|
45
|
-
getUsageInfo() {
|
46
|
-
return this._usageInfo;
|
47
|
-
}
|
48
|
-
|
49
|
-
}
|
@@ -1,92 +0,0 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
|
-
import ansiEscapes from 'ansi-escapes';
|
3
|
-
import stringLength from 'string-length';
|
4
|
-
import { PatternPrompt, printPatternCaret, printRestoredPatternCaret } from 'jest-watcher';
|
5
|
-
import { escapeStrForRegex } from 'jest-regex-util';
|
6
|
-
import { highlight, getTerminalWidth, trimAndFormatPath, removeTrimmingDots } from "../lib/utils.js";
|
7
|
-
import { formatTypeaheadSelection, printMore, printPatternMatches, printStartTyping, printTypeaheadItem } from "../lib/pattern_mode_helpers.js";
|
8
|
-
import scroll from "../lib/scroll.js";
|
9
|
-
export default class FileNamePatternPrompt extends PatternPrompt {
|
10
|
-
constructor(pipe, prompt) {
|
11
|
-
super(pipe, prompt);
|
12
|
-
this._entityName = 'filenames';
|
13
|
-
this._searchSources = [];
|
14
|
-
}
|
15
|
-
|
16
|
-
_onChange(pattern, options) {
|
17
|
-
super._onChange(pattern, options);
|
18
|
-
|
19
|
-
this._printTypeahead(pattern, options);
|
20
|
-
}
|
21
|
-
|
22
|
-
_printTypeahead(pattern, options) {
|
23
|
-
const matchedTests = this._getMatchedTests(pattern);
|
24
|
-
|
25
|
-
const total = matchedTests.length;
|
26
|
-
const pipe = this._pipe;
|
27
|
-
const prompt = this._prompt;
|
28
|
-
printPatternCaret(pattern, pipe);
|
29
|
-
pipe.write(ansiEscapes.cursorLeft);
|
30
|
-
|
31
|
-
if (pattern) {
|
32
|
-
printPatternMatches(total, 'file', pipe);
|
33
|
-
const prefix = ` ${chalk.dim('\u203A')} `;
|
34
|
-
const padding = stringLength(prefix) + 2;
|
35
|
-
const width = getTerminalWidth(pipe);
|
36
|
-
const {
|
37
|
-
start,
|
38
|
-
end,
|
39
|
-
index
|
40
|
-
} = scroll(total, options);
|
41
|
-
prompt.setPromptLength(total);
|
42
|
-
matchedTests.slice(start, end).map(({
|
43
|
-
path,
|
44
|
-
context
|
45
|
-
}) => {
|
46
|
-
const filePath = trimAndFormatPath(padding, context.config, path, width);
|
47
|
-
return highlight(path, filePath, pattern);
|
48
|
-
}).map((item, i) => formatTypeaheadSelection(item, i, index, prompt)).forEach(item => printTypeaheadItem(item, pipe));
|
49
|
-
|
50
|
-
if (total > end) {
|
51
|
-
printMore('file', pipe, total - end);
|
52
|
-
}
|
53
|
-
} else {
|
54
|
-
printStartTyping('filename', pipe);
|
55
|
-
}
|
56
|
-
|
57
|
-
printRestoredPatternCaret(pattern, this._currentUsageRows, pipe);
|
58
|
-
}
|
59
|
-
|
60
|
-
_getMatchedTests(pattern) {
|
61
|
-
let regex;
|
62
|
-
|
63
|
-
try {
|
64
|
-
regex = new RegExp(pattern, 'i');
|
65
|
-
} catch (e) {
|
66
|
-
return [];
|
67
|
-
}
|
68
|
-
|
69
|
-
return this._searchSources.reduce((tests, {
|
70
|
-
testPaths,
|
71
|
-
config
|
72
|
-
}) => {
|
73
|
-
return tests.concat(testPaths.filter(testPath => regex.test(testPath)).map(path => ({
|
74
|
-
path,
|
75
|
-
context: {
|
76
|
-
config
|
77
|
-
}
|
78
|
-
})));
|
79
|
-
}, []);
|
80
|
-
}
|
81
|
-
|
82
|
-
updateSearchSources(searchSources) {
|
83
|
-
this._searchSources = searchSources;
|
84
|
-
}
|
85
|
-
|
86
|
-
run(onSuccess, onCancel, options) {
|
87
|
-
super.run(value => {
|
88
|
-
onSuccess(removeTrimmingDots(value).split('/').map(escapeStrForRegex).join('/'));
|
89
|
-
}, onCancel, options);
|
90
|
-
}
|
91
|
-
|
92
|
-
}
|
package/build/index.js
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
|
-
import stripAnsi from 'strip-ansi';
|
3
|
-
|
4
|
-
const pluralize = (count, text) => count === 1 ? text : `${text}s`;
|
5
|
-
|
6
|
-
export const printPatternMatches = (count, entity, pipe, extraText = '') => {
|
7
|
-
const pluralized = pluralize(count, entity);
|
8
|
-
const result = count ? `\n\n Pattern matches ${count} ${pluralized}` : `\n\n Pattern matches no ${pluralized}`;
|
9
|
-
pipe.write(result + extraText);
|
10
|
-
};
|
11
|
-
export const printStartTyping = (entity, pipe) => {
|
12
|
-
pipe.write(`\n\n ${chalk.italic.yellow(`Start typing to filter by a ${entity} regex pattern.`)}`);
|
13
|
-
};
|
14
|
-
export const printMore = (entity, pipe, more) => {
|
15
|
-
pipe.write(`\n ${chalk.dim(`...and ${more} more ${pluralize(more, entity)}`)}`);
|
16
|
-
};
|
17
|
-
export const printTypeaheadItem = (item, pipe) => {
|
18
|
-
pipe.write(`\n ${chalk.dim('\u203A')} ${item}`);
|
19
|
-
};
|
20
|
-
export const formatTypeaheadSelection = (item, index, activeIndex, prompt) => {
|
21
|
-
if (index === activeIndex) {
|
22
|
-
prompt.setPromptSelection(stripAnsi(item));
|
23
|
-
return chalk.black.bgYellow(stripAnsi(item));
|
24
|
-
}
|
25
|
-
|
26
|
-
return item;
|
27
|
-
};
|
package/build/lib/scroll.js
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
const scroll = (size, {
|
2
|
-
offset,
|
3
|
-
max
|
4
|
-
}) => {
|
5
|
-
let start = 0;
|
6
|
-
let index = Math.min(offset, size);
|
7
|
-
const halfScreen = max / 2;
|
8
|
-
|
9
|
-
if (index <= halfScreen) {
|
10
|
-
start = 0;
|
11
|
-
} else {
|
12
|
-
if (size >= max) {
|
13
|
-
start = Math.min(index - halfScreen - 1, size - max);
|
14
|
-
}
|
15
|
-
|
16
|
-
index = Math.min(index - start, size);
|
17
|
-
}
|
18
|
-
|
19
|
-
return {
|
20
|
-
end: Math.min(size, start + max),
|
21
|
-
index,
|
22
|
-
start
|
23
|
-
};
|
24
|
-
};
|
25
|
-
|
26
|
-
export default scroll;
|
package/build/lib/utils.js
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
import path from 'path';
|
2
|
-
import chalk from 'chalk';
|
3
|
-
import slash from 'slash';
|
4
|
-
import stripAnsi from 'strip-ansi';
|
5
|
-
const TRIMMING_DOTS = '...';
|
6
|
-
const ENTER = '⏎';
|
7
|
-
|
8
|
-
const relativePath = (config, testPath) => {
|
9
|
-
const relativeTestPath = path.relative(config.cwd || config.rootDir, testPath);
|
10
|
-
const dirname = path.dirname(relativeTestPath);
|
11
|
-
const basename = path.basename(relativeTestPath);
|
12
|
-
return {
|
13
|
-
basename,
|
14
|
-
dirname
|
15
|
-
};
|
16
|
-
};
|
17
|
-
|
18
|
-
const colorize = (str, start, end) => chalk.dim(str.slice(0, start)) + chalk.reset(str.slice(start, end)) + chalk.dim(str.slice(end));
|
19
|
-
|
20
|
-
export const trimAndFormatPath = (pad, config, testPath, columns) => {
|
21
|
-
const maxLength = columns - pad;
|
22
|
-
const relative = relativePath(config, testPath);
|
23
|
-
const {
|
24
|
-
basename
|
25
|
-
} = relative;
|
26
|
-
let {
|
27
|
-
dirname
|
28
|
-
} = relative; // length is ok
|
29
|
-
|
30
|
-
if ((dirname + path.sep + basename).length <= maxLength) {
|
31
|
-
return slash(chalk.dim(dirname + path.sep) + chalk.bold(basename));
|
32
|
-
} // we can fit trimmed dirname and full basename
|
33
|
-
|
34
|
-
|
35
|
-
const basenameLength = basename.length;
|
36
|
-
|
37
|
-
if (basenameLength + 4 < maxLength) {
|
38
|
-
const dirnameLength = maxLength - 4 - basenameLength;
|
39
|
-
dirname = `${TRIMMING_DOTS}${dirname.slice(dirname.length - dirnameLength, dirname.length)}`;
|
40
|
-
return slash(chalk.dim(dirname + path.sep) + chalk.bold(basename));
|
41
|
-
}
|
42
|
-
|
43
|
-
if (basenameLength + 4 === maxLength) {
|
44
|
-
return slash(chalk.dim(`${TRIMMING_DOTS}${path.sep}`) + chalk.bold(basename));
|
45
|
-
} // can't fit dirname, but can fit trimmed basename
|
46
|
-
|
47
|
-
|
48
|
-
return slash(chalk.bold(`${TRIMMING_DOTS}${basename.slice(-maxLength + 3)}`));
|
49
|
-
};
|
50
|
-
export const getTerminalWidth = (pipe = process.stdout) => pipe.columns;
|
51
|
-
export const highlight = (rawPath, filePath, pattern) => {
|
52
|
-
const relativePathHead = './';
|
53
|
-
let regexp;
|
54
|
-
|
55
|
-
try {
|
56
|
-
regexp = new RegExp(pattern, 'i');
|
57
|
-
} catch (e) {
|
58
|
-
return chalk.dim(filePath);
|
59
|
-
}
|
60
|
-
|
61
|
-
const strippedRawPath = stripAnsi(rawPath);
|
62
|
-
const strippedFilePath = stripAnsi(filePath);
|
63
|
-
const match = strippedRawPath.match(regexp);
|
64
|
-
|
65
|
-
if (!match || match.index == null) {
|
66
|
-
return chalk.dim(strippedFilePath);
|
67
|
-
}
|
68
|
-
|
69
|
-
const offset = strippedRawPath.length - strippedFilePath.length;
|
70
|
-
let trimLength;
|
71
|
-
|
72
|
-
if (strippedFilePath.startsWith(TRIMMING_DOTS)) {
|
73
|
-
trimLength = TRIMMING_DOTS.length;
|
74
|
-
} else if (strippedFilePath.startsWith(relativePathHead)) {
|
75
|
-
trimLength = relativePathHead.length;
|
76
|
-
} else {
|
77
|
-
trimLength = 0;
|
78
|
-
}
|
79
|
-
|
80
|
-
const start = match.index - offset;
|
81
|
-
const end = start + match[0].length;
|
82
|
-
return colorize(strippedFilePath, Math.max(start, 0), Math.max(end, trimLength));
|
83
|
-
};
|
84
|
-
export const formatTestNameByPattern = (testName, pattern, width) => {
|
85
|
-
const inlineTestName = testName.replace(/(\r\n|\n|\r)/gm, ENTER);
|
86
|
-
let regexp;
|
87
|
-
|
88
|
-
try {
|
89
|
-
regexp = new RegExp(pattern, 'i');
|
90
|
-
} catch (e) {
|
91
|
-
return chalk.dim(inlineTestName);
|
92
|
-
}
|
93
|
-
|
94
|
-
const match = inlineTestName.match(regexp);
|
95
|
-
|
96
|
-
if (!match || match.index == null) {
|
97
|
-
return chalk.dim(inlineTestName);
|
98
|
-
}
|
99
|
-
|
100
|
-
const startPatternIndex = Math.max(match.index, 0);
|
101
|
-
const endPatternIndex = startPatternIndex + match[0].length;
|
102
|
-
const testNameFitsInTerminal = inlineTestName.length <= width;
|
103
|
-
|
104
|
-
if (testNameFitsInTerminal) {
|
105
|
-
return colorize(inlineTestName, startPatternIndex, endPatternIndex);
|
106
|
-
}
|
107
|
-
|
108
|
-
const numberOfTruncatedChars = TRIMMING_DOTS.length + inlineTestName.length - width;
|
109
|
-
const end = Math.max(endPatternIndex - numberOfTruncatedChars, 0);
|
110
|
-
const truncatedTestName = inlineTestName.slice(numberOfTruncatedChars);
|
111
|
-
const shouldHighlightDots = startPatternIndex <= numberOfTruncatedChars;
|
112
|
-
|
113
|
-
if (shouldHighlightDots) {
|
114
|
-
return colorize(TRIMMING_DOTS + truncatedTestName, 0, end + TRIMMING_DOTS.length);
|
115
|
-
}
|
116
|
-
|
117
|
-
const start = startPatternIndex - numberOfTruncatedChars;
|
118
|
-
return colorize(TRIMMING_DOTS + truncatedTestName, start + TRIMMING_DOTS.length, end + TRIMMING_DOTS.length);
|
119
|
-
};
|
120
|
-
export const removeTrimmingDots = value => {
|
121
|
-
if (value.startsWith(TRIMMING_DOTS)) {
|
122
|
-
return value.slice(TRIMMING_DOTS.length);
|
123
|
-
}
|
124
|
-
|
125
|
-
return value;
|
126
|
-
};
|
@@ -1,49 +0,0 @@
|
|
1
|
-
import { Prompt } from 'jest-watcher';
|
2
|
-
import TestNamePatternPrompt from "./prompt.js";
|
3
|
-
export default class TestNamePlugin {
|
4
|
-
constructor({
|
5
|
-
stdin,
|
6
|
-
stdout,
|
7
|
-
config = {}
|
8
|
-
}) {
|
9
|
-
this._stdin = stdin;
|
10
|
-
this._stdout = stdout;
|
11
|
-
this._prompt = new Prompt();
|
12
|
-
this._testResults = [];
|
13
|
-
this._usageInfo = {
|
14
|
-
key: config.key || 't',
|
15
|
-
prompt: config.prompt || 'filter by a test name regex pattern'
|
16
|
-
};
|
17
|
-
}
|
18
|
-
|
19
|
-
apply(jestHooks) {
|
20
|
-
jestHooks.onTestRunComplete(({
|
21
|
-
testResults
|
22
|
-
}) => {
|
23
|
-
this._testResults = testResults;
|
24
|
-
});
|
25
|
-
}
|
26
|
-
|
27
|
-
onKey(key) {
|
28
|
-
this._prompt.put(key);
|
29
|
-
}
|
30
|
-
|
31
|
-
run(globalConfig, updateConfigAndRun) {
|
32
|
-
const p = new TestNamePatternPrompt(this._stdout, this._prompt);
|
33
|
-
p.updateCachedTestResults(this._testResults);
|
34
|
-
return new Promise((res, rej) => {
|
35
|
-
p.run(testNamePattern => {
|
36
|
-
updateConfigAndRun({
|
37
|
-
mode: 'watch',
|
38
|
-
testNamePattern
|
39
|
-
});
|
40
|
-
res();
|
41
|
-
}, rej);
|
42
|
-
});
|
43
|
-
}
|
44
|
-
|
45
|
-
getUsageInfo() {
|
46
|
-
return this._usageInfo;
|
47
|
-
}
|
48
|
-
|
49
|
-
}
|
@@ -1,86 +0,0 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
|
-
import ansiEscapes from 'ansi-escapes';
|
3
|
-
import { PatternPrompt, printPatternCaret, printRestoredPatternCaret } from 'jest-watcher';
|
4
|
-
import { escapeStrForRegex } from 'jest-regex-util';
|
5
|
-
import scroll from "../lib/scroll.js";
|
6
|
-
import { formatTestNameByPattern, getTerminalWidth, removeTrimmingDots } from "../lib/utils.js";
|
7
|
-
import { formatTypeaheadSelection, printMore, printPatternMatches, printStartTyping, printTypeaheadItem } from "../lib/pattern_mode_helpers.js";
|
8
|
-
export default class TestNamePatternPrompt extends PatternPrompt {
|
9
|
-
constructor(pipe, prompt) {
|
10
|
-
super(pipe, prompt);
|
11
|
-
this._entityName = 'tests';
|
12
|
-
this._cachedTestResults = [];
|
13
|
-
this._offset = -1;
|
14
|
-
}
|
15
|
-
|
16
|
-
_onChange(pattern, options) {
|
17
|
-
super._onChange(pattern, options);
|
18
|
-
|
19
|
-
this._offset = options.offset;
|
20
|
-
|
21
|
-
this._printTypeahead(pattern, options);
|
22
|
-
}
|
23
|
-
|
24
|
-
_printTypeahead(pattern, options) {
|
25
|
-
const matchedTests = this._getMatchedTests(pattern);
|
26
|
-
|
27
|
-
const total = matchedTests.length;
|
28
|
-
const pipe = this._pipe;
|
29
|
-
const prompt = this._prompt;
|
30
|
-
printPatternCaret(pattern, pipe);
|
31
|
-
pipe.write(ansiEscapes.cursorLeft);
|
32
|
-
|
33
|
-
if (pattern) {
|
34
|
-
printPatternMatches(total, 'test', pipe, ` from ${chalk.yellow('cached')} test suites`);
|
35
|
-
const width = getTerminalWidth(pipe);
|
36
|
-
const {
|
37
|
-
start,
|
38
|
-
end,
|
39
|
-
index
|
40
|
-
} = scroll(total, options);
|
41
|
-
prompt.setPromptLength(total);
|
42
|
-
matchedTests.slice(start, end).map(name => formatTestNameByPattern(name, pattern, width - 4)).map((item, i) => formatTypeaheadSelection(item, i, index, prompt)).forEach(item => printTypeaheadItem(item, pipe));
|
43
|
-
|
44
|
-
if (total > end) {
|
45
|
-
printMore('test', pipe, total - end);
|
46
|
-
}
|
47
|
-
} else {
|
48
|
-
printStartTyping('test name', pipe);
|
49
|
-
}
|
50
|
-
|
51
|
-
printRestoredPatternCaret(pattern, this._currentUsageRows, pipe);
|
52
|
-
}
|
53
|
-
|
54
|
-
_getMatchedTests(pattern) {
|
55
|
-
let regex;
|
56
|
-
|
57
|
-
try {
|
58
|
-
regex = new RegExp(pattern, 'i');
|
59
|
-
} catch (e) {
|
60
|
-
return [];
|
61
|
-
}
|
62
|
-
|
63
|
-
return this._cachedTestResults.reduce((matchedTests, {
|
64
|
-
testResults
|
65
|
-
}) => {
|
66
|
-
return matchedTests.concat(testResults.filter(({
|
67
|
-
fullName
|
68
|
-
}) => regex.test(fullName)).map(({
|
69
|
-
fullName
|
70
|
-
}) => fullName));
|
71
|
-
}, []);
|
72
|
-
}
|
73
|
-
|
74
|
-
updateCachedTestResults(testResults = []) {
|
75
|
-
this._cachedTestResults = testResults;
|
76
|
-
}
|
77
|
-
|
78
|
-
run(onSuccess, onCancel, options) {
|
79
|
-
super.run(value => {
|
80
|
-
const preparedPattern = escapeStrForRegex(removeTrimmingDots(value));
|
81
|
-
const useExactMatch = this._offset !== -1;
|
82
|
-
onSuccess(useExactMatch ? `^${preparedPattern}$` : preparedPattern);
|
83
|
-
}, onCancel, options);
|
84
|
-
}
|
85
|
-
|
86
|
-
}
|
package/build/types/Config.js
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
export {};
|