Skip to content

Commit

Permalink
fix: allow selecting tests and files containing regexp special charac…
Browse files Browse the repository at this point in the history
…ters
  • Loading branch information
Andarist authored and SimenB committed Nov 9, 2019
1 parent 17d471f commit e6f4851
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 26 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dependencies": {
"ansi-escapes": "^4.2.1",
"chalk": "^2.4.1",
"jest-regex-util": "^24.9.0",
"jest-watcher": "^24.3.0",
"slash": "^3.0.0",
"string-length": "^3.1.0",
Expand Down
64 changes: 61 additions & 3 deletions src/file_name_plugin/__tests__/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,34 @@ it('can use arrows to select a specific file', async () => {

expect(updateConfigAndRun).toHaveBeenCalledWith({
mode: 'watch',
testPathPattern: 'src/file-1.js',
testPathPattern: 'src/file-1\\.js',
});
});

it('can select a specific file that includes a regexp special character', async () => {
const { hookEmitter, updateConfigAndRun, plugin, type } = pluginTester(
FileNamePlugin,
);

hookEmitter.onFileChange({
projects: [
{
config: {
rootDir: '/project',
},
testPaths: ['/project/src/file_(xyz).js'],
},
],
});
const runPromise = plugin.run({}, updateConfigAndRun);

type('x', 'y', 'z', KEYS.ARROW_DOWN, KEYS.ENTER);

await runPromise;

expect(updateConfigAndRun).toHaveBeenCalledWith({
mode: 'watch',
testPathPattern: 'src/file_\\(xyz\\)\\.js',
});
});

Expand Down Expand Up @@ -82,8 +109,10 @@ it('can select a pattern that matches multiple files', async () => {

it('can configure the key and prompt', async () => {
const { plugin } = pluginTester(FileNamePlugin, {
key: 'l',
prompt: 'have a custom prompt',
config: {
key: 'l',
prompt: 'have a custom prompt',
},
});

expect(plugin.getUsageInfo()).toEqual({
Expand All @@ -110,3 +139,32 @@ it('file matching is case insensitive', async () => {
type(KEYS.ENTER);
await runPromise;
});

it("selected file doesn't include trimming dots", async () => {
const { hookEmitter, updateConfigAndRun, plugin, type } = pluginTester(
FileNamePlugin,
{
stdout: { columns: 40 },
},
);

hookEmitter.onFileChange({
projects: [
{
config: {
rootDir: '/project',
},
testPaths: ['/project/src/long_name_gonna_need_trimming.js'],
},
],
});
const runPromise = plugin.run({}, updateConfigAndRun);

type('t', 'r', 'i', 'm', 'm', KEYS.ARROW_DOWN, KEYS.ENTER);
await runPromise;

expect(updateConfigAndRun).toHaveBeenCalledWith({
mode: 'watch',
testPathPattern: 'ing\\.js',
});
});
10 changes: 9 additions & 1 deletion src/file_name_plugin/plugin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// @flow

import { Prompt } from 'jest-watcher';
import { escapeStrForRegex } from 'jest-regex-util';
import FileNamePatternPrompt, { type SearchSources } from './prompt';
import { removeTrimmingDots } from '../lib/utils';

type PluginConfig = {
key?: string,
Expand Down Expand Up @@ -53,7 +55,13 @@ class FileNamePlugin {
p.updateSearchSources(this._projects);
return new Promise((res, rej) => {
p.run(value => {
updateConfigAndRun({ mode: 'watch', testPathPattern: value });
updateConfigAndRun({
mode: 'watch',
testPathPattern: removeTrimmingDots(value)
.split('/')
.map(escapeStrForRegex)
.join('/'),
});
res();
}, rej);
});
Expand Down
45 changes: 30 additions & 15 deletions src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import slash from 'slash';
import stripAnsi from 'strip-ansi';
import type { ProjectConfig } from '../types/Config';

const TRIMMING_DOTS = '...';
const ENTER = '⏎';

const relativePath = (config: ProjectConfig, testPath: string) => {
testPath = path.relative(config.cwd || config.rootDir, testPath);
const dirname = path.dirname(testPath);
Expand Down Expand Up @@ -38,21 +41,25 @@ export const trimAndFormatPath = (
const basenameLength = basename.length;
if (basenameLength + 4 < maxLength) {
const dirnameLength = maxLength - 4 - basenameLength;
dirname = `...${dirname.slice(
dirname = `${TRIMMING_DOTS}${dirname.slice(
dirname.length - dirnameLength,
dirname.length,
)}`;
return slash(chalk.dim(dirname + path.sep) + chalk.bold(basename));
}

if (basenameLength + 4 === maxLength) {
return slash(chalk.dim(`...${path.sep}`) + chalk.bold(basename));
return slash(
chalk.dim(`${TRIMMING_DOTS}${path.sep}`) + chalk.bold(basename),
);
}

// can't fit dirname, but can fit trimmed basename
return slash(
chalk.bold(
`...${basename.slice(basename.length - maxLength - 4, basename.length)}`,
`${TRIMMING_DOTS}${basename.slice(
basename.length - maxLength - 4,
basename.length,
)}`,
),
);
};
Expand All @@ -68,7 +75,6 @@ export const highlight = (
pattern: string,
rootDir: string,
) => {
const trim = '...';
const relativePathHead = './';

let regexp;
Expand All @@ -90,9 +96,9 @@ export const highlight = (
let offset;
let trimLength;

if (filePath.startsWith(trim)) {
if (filePath.startsWith(TRIMMING_DOTS)) {
offset = rawPath.length - filePath.length;
trimLength = trim.length;
trimLength = TRIMMING_DOTS.length;
} else if (filePath.startsWith(relativePathHead)) {
offset = rawPath.length - filePath.length;
trimLength = relativePathHead.length;
Expand All @@ -106,9 +112,6 @@ export const highlight = (
return colorize(filePath, Math.max(start, 0), Math.max(end, trimLength));
};

const DOTS = '...';
const ENTER = '⏎';

export const formatTestNameByPattern = (
testName: string,
pattern: string,
Expand Down Expand Up @@ -139,19 +142,31 @@ export const formatTestNameByPattern = (
return colorize(inlineTestName, startPatternIndex, endPatternIndex);
}

const numberOfTruncatedChars = DOTS.length + inlineTestName.length - width;
const numberOfTruncatedChars =
TRIMMING_DOTS.length + inlineTestName.length - width;
const end = Math.max(endPatternIndex - numberOfTruncatedChars, 0);
const truncatedTestName = inlineTestName.slice(numberOfTruncatedChars);

const shouldHighlightDots = startPatternIndex <= numberOfTruncatedChars;
if (shouldHighlightDots) {
return colorize(DOTS + truncatedTestName, 0, end + DOTS.length);
return colorize(
TRIMMING_DOTS + truncatedTestName,
0,
end + TRIMMING_DOTS.length,
);
}

const start = startPatternIndex - numberOfTruncatedChars;
return colorize(
DOTS + truncatedTestName,
start + DOTS.length,
end + DOTS.length,
TRIMMING_DOTS + truncatedTestName,
start + TRIMMING_DOTS.length,
end + TRIMMING_DOTS.length,
);
};

export const removeTrimmingDots = (value: string): string => {
if (value.startsWith(TRIMMING_DOTS)) {
return value.slice(TRIMMING_DOTS.length);
}
return value;
};
63 changes: 61 additions & 2 deletions src/test_name_plugin/__tests__/plugin.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,38 @@ it('can select a pattern that matches a describe block', async () => {
});
});

it('can select a pattern that includes a regexp special character', async () => {
const { hookEmitter, updateConfigAndRun, plugin, type } = pluginTester(
TestNamePlugin,
);

hookEmitter.onTestRunComplete({
testResults: [
{
testResults: [
{ title: 'bracket', fullName: 'bracket description (foo)' },
],
},
],
});
const runPromise = plugin.run({}, updateConfigAndRun);

type('b', 'r', KEYS.ARROW_DOWN, KEYS.ENTER);

await runPromise;

expect(updateConfigAndRun).toHaveBeenCalledWith({
mode: 'watch',
testNamePattern: 'bracket description \\(foo\\)',
});
});

it('can configure the key and prompt', async () => {
const { plugin } = pluginTester(TestNamePlugin, {
key: 'l',
prompt: 'have a custom prompt',
config: {
key: 'l',
prompt: 'have a custom prompt',
},
});

expect(plugin.getUsageInfo()).toEqual({
Expand All @@ -140,3 +168,34 @@ it('test matching is case insensitive', async () => {
type(KEYS.ENTER);
await runPromise;
});

it("selected pattern doesn't include trimming dots", async () => {
const { hookEmitter, updateConfigAndRun, plugin, type } = pluginTester(
TestNamePlugin,
{
stdout: { columns: 30 },
},
);

hookEmitter.onTestRunComplete({
testResults: [
{
testResults: [
{
title: 'trimmed long',
fullName: 'long test name, gonna need trimming',
},
],
},
],
});
const runPromise = plugin.run({}, updateConfigAndRun);

type('t', 'r', 'i', 'm', 'm', KEYS.ARROW_DOWN, KEYS.ENTER);
await runPromise;

expect(updateConfigAndRun).toHaveBeenCalledWith({
mode: 'watch',
testNamePattern: 'me, gonna need trimming',
});
});
7 changes: 6 additions & 1 deletion src/test_name_plugin/plugin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// @flow

import { Prompt } from 'jest-watcher';
import { escapeStrForRegex } from 'jest-regex-util';
import TestNamePatternPrompt, { type TestResult } from './prompt';
import { removeTrimmingDots } from '../lib/utils';

type PluginConfig = {
key?: string,
Expand Down Expand Up @@ -53,7 +55,10 @@ class TestNamePlugin {
p.updateCachedTestResults(this._testResults);
return new Promise((res, rej) => {
p.run(value => {
updateConfigAndRun({ mode: 'watch', testNamePattern: value });
updateConfigAndRun({
mode: 'watch',
testNamePattern: escapeStrForRegex(removeTrimmingDots(value)),
});
res();
}, rej);
});
Expand Down
9 changes: 6 additions & 3 deletions src/test_utils/pluginTester.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ expect.addSnapshotSerializer({
print: val => stripAnsi(val.replace(WINDOWS_CLEAR, '[MOCK - clear]')),
});

const pluginTester = (Plugin, config = {}) => {
const stdout = { columns: 80, write: jest.fn() };
const pluginTester = (Plugin, options = {}) => {
const stdout = {
columns: (options.stdout || {}).columns || 80,
write: jest.fn(),
};
const jestHooks = new JestHook();
const plugin = new Plugin({ stdout, config });
const plugin = new Plugin({ stdout, config: options.config });
plugin.apply(jestHooks.getSubscriber());

const type = (...keys) => keys.forEach(key => plugin.onKey(key));
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4204,7 +4204,7 @@ rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3:

rimraf@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==
dependencies:
glob "^7.1.3"
Expand Down

0 comments on commit e6f4851

Please sign in to comment.