Skip to content

Commit f3fce7b

Browse files
seanpoultercpojer
authored andcommitted
Fix --testPathPattern escaping for '\\' on Windows (#5230)
PR #5054 introduced a regression when handling escaped Windows path separators in the `--testPathPattern`. The PR applied the same escaping as the "Watch Usage" prompt, which incorrectly escapes `path\\.*file` as `path\\\.*file`. This commit fixes the regular expression used in "Watch Usage" and the `--testPathPattern` CLI argument with unit tests.
1 parent 6896ee6 commit f3fce7b

4 files changed

Lines changed: 141 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
* `[jest-config]` fix unexpected condition to avoid infinite recursion in
66
Windows platform. ([#5161](https://github.com/facebook/jest/pull/5161))
7+
* `[jest-regex-util]` Fix breaking change in `--testPathPattern`
8+
([#5230](https://github.com/facebook/jest/pull/5230))
79

810
### Features
911

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`replacePathSepForRegex() posix should match the expected output from #5216 1`] = `
4+
Array [
5+
"jest-config\\\\/.*normalize",
6+
"jest-config/.*normalize",
7+
"jest-config\\\\.*normalize",
8+
"jest-config\\\\\\\\.*normalize",
9+
]
10+
`;
11+
12+
exports[`replacePathSepForRegex() win32 should match the expected output from #5216 1`] = `
13+
Array [
14+
"jest-config\\\\\\\\\\\\\\\\.*normalize",
15+
"jest-config\\\\\\\\.*normalize",
16+
"jest-config\\\\.*normalize",
17+
"jest-config\\\\\\\\.*normalize",
18+
]
19+
`;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
jest.mock('path');
2+
3+
import {replacePathSepForRegex} from '../index';
4+
import path from 'path';
5+
6+
describe('replacePathSepForRegex()', () => {
7+
const testPatternsFrom5216 = [
8+
'jest-config\\/.*normalize',
9+
'jest-config/.*normalize',
10+
'jest-config\\.*normalize',
11+
'jest-config\\\\.*normalize',
12+
];
13+
14+
describe('posix', () => {
15+
beforeEach(() => (path.sep = '/'));
16+
17+
it('should return the path', () => {
18+
const expected = {};
19+
expect(replacePathSepForRegex(expected)).toBe(expected);
20+
});
21+
22+
// Confirming existing behavior; could be changed to improve cross-platform support
23+
it('should not replace Windows path separators', () => {
24+
expect(replacePathSepForRegex('a\\.*b')).toBe('a\\.*b');
25+
expect(replacePathSepForRegex('a\\\\.*b')).toBe('a\\\\.*b');
26+
});
27+
28+
// Bonus: Test cases from https://github.com/facebook/jest#5216
29+
it('should match the expected output from #5216', () => {
30+
expect(
31+
testPatternsFrom5216.map(replacePathSepForRegex),
32+
).toMatchSnapshot();
33+
});
34+
});
35+
36+
describe('win32', () => {
37+
beforeEach(() => (path.sep = '\\'));
38+
39+
it('should escape Windows path separators', () => {
40+
expect(replacePathSepForRegex('a\\b\\c')).toBe('a\\\\b\\\\c');
41+
});
42+
43+
it('should replace POSIX path separators', () => {
44+
expect(replacePathSepForRegex('a/b/c')).toBe('a\\\\b\\\\c');
45+
});
46+
47+
it('should not escape an escaped period', () => {
48+
expect(replacePathSepForRegex('a\\.dotfile')).toBe('a\\.dotfile');
49+
expect(replacePathSepForRegex('a\\\\\\.dotfile')).toBe('a\\\\\\.dotfile');
50+
});
51+
52+
it('should not escape an escaped Windows path separator', () => {
53+
expect(replacePathSepForRegex('a\\\\b')).toBe('a\\\\b');
54+
expect(replacePathSepForRegex('a\\\\.dotfile')).toBe('a\\\\.dotfile');
55+
});
56+
57+
// Confirming existing behavior; could be changed to improve cross-platform support
58+
it('should not replace escaped POSIX separators', () => {
59+
expect(replacePathSepForRegex('a\\/b')).toBe('a\\\\\\\\b');
60+
});
61+
62+
// Bonus: Test cases from https://github.com/facebook/jest#5216
63+
it('should match the expected output from #5216', () => {
64+
expect(
65+
testPatternsFrom5216.map(replacePathSepForRegex),
66+
).toMatchSnapshot();
67+
});
68+
});
69+
});

packages/jest-regex-util/src/index.js

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,56 @@ export const escapeStrForRegex = (string: string) =>
2222
string.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
2323

2424
export const replacePathSepForRegex = (string: string) => {
25-
if (path.sep === '\\') {
26-
return string.replace(/(\/|\\(?!\.))/g, '\\\\');
25+
if (!string || path.sep !== '\\') {
26+
return string;
27+
}
28+
29+
let result = '';
30+
for (let i = 0; i < string.length; i += 1) {
31+
const char = string[i];
32+
if (char === '\\') {
33+
const nextChar = string[i + 1];
34+
/* Case: \/ -- recreate legacy behavior */
35+
if (nextChar === '/') {
36+
i += 1;
37+
result += '\\\\\\\\';
38+
continue;
39+
}
40+
41+
/* Case: \. */
42+
if (nextChar === '.') {
43+
i += 1;
44+
result += '\\.';
45+
continue;
46+
}
47+
48+
/* Case: \\. */
49+
if (nextChar === '\\' && string[i + 2] === '.') {
50+
i += 2;
51+
result += '\\\\.';
52+
continue;
53+
}
54+
55+
/* Case: \\ */
56+
if (nextChar === '\\') {
57+
i += 1;
58+
result += '\\\\';
59+
continue;
60+
}
61+
62+
/* Case: \<other> */
63+
result += '\\\\';
64+
continue;
65+
}
66+
67+
/* Case: / */
68+
if (char === '/') {
69+
result += '\\\\';
70+
continue;
71+
}
72+
73+
result += char;
2774
}
28-
return string;
75+
76+
return result;
2977
};

0 commit comments

Comments
 (0)