Skip to content

Commit 47ec759

Browse files
authored
handle multi-line test names (#1234)
escape newline for both jest runs and debugging. resolve #1204
1 parent 92b7162 commit 47ec759

File tree

3 files changed

+44
-23
lines changed

3 files changed

+44
-23
lines changed

src/helpers.ts

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,37 @@ export const getDefaultJestCommand = (rootPath = ''): string | undefined => {
126126

127127
/**
128128
* Escapes special characters in a string to be used as a regular expression pattern.
129-
* @param str - The string to escape.
130-
* @returns The escaped string.
129+
* If the provided value is already a regex (indicated by `isRegExp`), it is returned unmodified.
131130
*
132-
* Note: the conversion algorithm is taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
131+
* [2025.04.14] Additionally, the function can replace actual newline characters with the literal sequence `\n`.
132+
* This behavior is enabled by default (via `replaceNewLine = true`) to ensure that
133+
* the regex pattern can be safely passed as a single command line argument—for example,
134+
* for options like "--testPathPattern" or "testNamePattern". Even for regex usage, modifying
135+
* newlines in this way can help prevent issues with multi-line arguments that might otherwise
136+
* break in shell contexts.
137+
*
138+
* @param {string | StringPattern} str - The string or object to escape. When an object is provided,
139+
* it must have a `value` property containing the string, and can optionally include:
140+
* - `isRegExp`: if true, the function assumes the string is already a valid regex and returns it as-is.
141+
* - `exactMatch`: if true, the resulting escaped string is anchored with '^' at the start and '$' at the end.
142+
* @param {boolean} [replaceNewLine=true] - Whether to replace newline characters with the literal
143+
* sequence "\n". This is beneficial when the result is used as a command line argument, even though
144+
* it alters the actual newline characters that might be expected in some regex patterns.
145+
*
146+
* @returns {string} The escaped string, suitable for use as a regex pattern and for passing as a shell argument.
147+
*
148+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
133149
*/
134-
export function escapeRegExp(str: string | StringPattern): string {
150+
export function escapeRegExp(str: string | StringPattern, replaceNewLine = true): string {
135151
const sp: StringPattern = typeof str === 'string' ? { value: str } : str;
152+
let value: string;
136153
if (sp.isRegExp) {
137-
return sp.value;
138-
}
139-
const escaped = sp.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
140-
if (sp.exactMatch) {
141-
return escaped + '$';
154+
value = sp.value;
155+
} else {
156+
const escaped = sp.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
157+
value = sp.exactMatch ? escaped + '$' : escaped;
142158
}
143-
return escaped;
159+
return replaceNewLine ? value.replace(/\r\n|\r|\n/g, '\\n') : value;
144160
}
145161

146162
/**

src/test-provider/test-item-data.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -798,10 +798,7 @@ export class TestData extends TestResultData {
798798
}
799799

800800
private getTestNamePattern(): TestNamePattern {
801-
if (isDataNode(this.node)) {
802-
return { value: this.node.fullName, exactMatch: true };
803-
}
804-
return { value: this.node.fullName, exactMatch: false };
801+
return { value: this.node.fullName, exactMatch: isDataNode(this.node) || false };
805802
}
806803

807804
getJestRunRequest(options?: ScheduleTestOptions): JestExtRequestType {

tests/helpers.test.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,26 @@ describe('ModuleHelpers', () => {
148148

149149
describe('escapeRegExp', () => {
150150
it.each`
151-
str | expected
152-
${'no special char'} | ${'no special char'}
153-
${'with (a)'} | ${'with \\(a\\)'}
154-
${'with {} and $sign'} | ${'with \\{\\} and \\$sign'}
155-
${'with []'} | ${'with \\[\\]'}
156-
${{ value: 'with []', exactMatch: true }} | ${'with \\[\\]$'}
157-
${{ value: 'with []', exactMatch: false }} | ${'with \\[\\]'}
158-
${{ value: 'with []', isRegExp: true }} | ${'with []'}
159-
${{ value: 'with []', isRegExp: false }} | ${'with \\[\\]'}
151+
case | str | expected
152+
${1} | ${'no special char'} | ${'no special char'}
153+
${2} | ${'with (a)'} | ${'with \\(a\\)'}
154+
${3} | ${'with {} and $sign'} | ${'with \\{\\} and \\$sign'}
155+
${4} | ${'with []'} | ${'with \\[\\]'}
156+
${5} | ${{ value: 'with []', exactMatch: true }} | ${'with \\[\\]$'}
157+
${6} | ${{ value: 'with []', exactMatch: false }} | ${'with \\[\\]'}
158+
${7} | ${{ value: 'with []', isRegExp: true }} | ${'with []'}
159+
${8} | ${{ value: 'with []', isRegExp: false }} | ${'with \\[\\]'}
160+
${9} | ${'with\nnewline'} | ${'with\\nnewline'}
161+
${10} | ${'with\r\nnewline'} | ${'with\\nnewline'}
162+
${11} | ${{ value: 'with\nnewline', isRegExp: false }} | ${'with\\nnewline'}
163+
${12} | ${{ value: 'with\nnewline', isRegExp: true }} | ${'with\\nnewline'}
160164
`('escapeRegExp: $str', ({ str, expected }) => {
161165
expect(escapeRegExp(str)).toEqual(expected);
162166
});
167+
it('can skip newline handling', () => {
168+
expect(escapeRegExp('with\nnewline', true)).toEqual('with\\nnewline');
169+
expect(escapeRegExp('with\nnewline', false)).toEqual('with\nnewline');
170+
});
163171
});
164172
describe('testIdString', () => {
165173
it.each`

0 commit comments

Comments
 (0)