Repository: jest-community/vscode-jest

Issue:
[BUG] Multiline test names don't work properly
**Describe the bug**

My test names look like this:

```ts
    it(`
      Given a user requesting another user's personal info
      When they call the endpoint
      Then it returns a status 403
    `, async () => {
```



**To Reproduce**
Steps to reproduce the behavior:
1. Name a test with a multiline template string
2. Click on the button to run the single test
3. See that the `\n` are not properly escaped when sent to the jest command line

**Expected behavior**

It should escape the `\n` of the test name to a string `\n      Given a user requesting another user's personal info\n      When they call the endpoint` in shell

**Screenshots**


**Environment (please complete the following information):**
- vscode-jest version: v6.4.0
- `node -v`: 20.18
- `npm -v` or `yarn --version`: [e.g., npm 10.1.0]
- jest or react-scripts (if you haven’t ejected) version: [e.g., jest 29.7.0]
- your vscode-jest settings:
```
{
  "jest.enable": true,
  "jest.runMode": "on-demand",
  "jest.virtualFolders": [
    {
      "name": "unit",
      "runMode": "on-save",
      "jestCommandLine": "yarn test"
    },
    {
      "name": "e2e",
      "runMode": "on-demand",
      "jestCommandLine": "yarn test:e2e"
    }
  ]
}
```

- Operating system: MacOS 14.4.1

**Prerequisite**
- are you able to run jest from the command line? yes
- where do you run jest CLI from? root directory
- how do you run your tests from the command line? yarn test

**Additional context**
Add any other context about the problem here.

---

The fastest (and the most fun) way to resolve the issue is to submit a pull request yourself. If you are interested, please check out the [contribution guide](https://github.com/jest-community/vscode-jest/CONTRIBUTING.md), we look forward to seeing your PR...


Hints from issue comments:
@pierpo: I guess the solution would probably be inside this: https://github.com/jest-community/vscode-jest/blob/fae16e86446379f907079dab9129041869d1c585/src/JestProcessManagement/JestProcess.ts#L196

But I never managed to build the extension myself and run it locally so I can't manage to contribute 🙈 @connectdotz: It's indeed an issue. Thanks for raising it.@connectdotz: @pierpo, feel free to take a look of PR #1234. It turns out we can't only fix the `shellQuote` as the debugger, which doesn't call `shellQuote`, also fails for newline test names...@pierpo: Aaaaah, too bad! Any idea regarding the debugger? At what point does it fail? Does the node CLI does not accept the escaped string?

Having the fix to run tests without debugger would already be good news, to be honest 😁  It's still half of the fix haha@connectdotz: @pierpo, In order to fix both run and debug, in #1234, I decided to put the newline handling in the `escapeRegExp()`, which is used by both run and debug. Anyway, I will merge it into the pre-release v6.4.2 soon. Stay tuned.

Solution patch:
diff --git a/src/helpers.ts b/src/helpers.ts
index a6ed985..35eda45 100644
--- a/src/helpers.ts
+++ b/src/helpers.ts
@@ -126,21 +126,37 @@ export const getDefaultJestCommand = (rootPath = ''): string | undefined => {
 
 /**
  * Escapes special characters in a string to be used as a regular expression pattern.
- * @param str - The string to escape.
- * @returns The escaped string.
+ * If the provided value is already a regex (indicated by `isRegExp`), it is returned unmodified.
  *
- * Note: the conversion algorithm is taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
+ * [2025.04.14] Additionally, the function can replace actual newline characters with the literal sequence `\n`.
+ * This behavior is enabled by default (via `replaceNewLine = true`) to ensure that
+ * the regex pattern can be safely passed as a single command line argument—for example,
+ * for options like "--testPathPattern" or "testNamePattern". Even for regex usage, modifying
+ * newlines in this way can help prevent issues with multi-line arguments that might otherwise
+ * break in shell contexts.
+ *
+ * @param {string | StringPattern} str - The string or object to escape. When an object is provided,
+ *   it must have a `value` property containing the string, and can optionally include:
+ *   - `isRegExp`: if true, the function assumes the string is already a valid regex and returns it as-is.
+ *   - `exactMatch`: if true, the resulting escaped string is anchored with '^' at the start and '$' at the end.
+ * @param {boolean} [replaceNewLine=true] - Whether to replace newline characters with the literal
+ *   sequence "\n". This is beneficial when the result is used as a command line argument, even though
+ *   it alters the actual newline characters that might be expected in some regex patterns.
+ *
+ * @returns {string} The escaped string, suitable for use as a regex pattern and for passing as a shell argument.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
  */
-export function escapeRegExp(str: string | StringPattern): string {
+export function escapeRegExp(str: string | StringPattern, replaceNewLine = true): string {
   const sp: StringPattern = typeof str === 'string' ? { value: str } : str;
+  let value: string;
   if (sp.isRegExp) {
-    return sp.value;
-  }
-  const escaped = sp.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
-  if (sp.exactMatch) {
-    return escaped + '$';
+    value = sp.value;
+  } else {
+    const escaped = sp.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+    value = sp.exactMatch ? escaped + '$' : escaped;
   }
-  return escaped;
+  return replaceNewLine ? value.replace(/\r\n|\r|\n/g, '\\n') : value;
 }
 
 /**
diff --git a/src/test-provider/test-item-data.ts b/src/test-provider/test-item-data.ts
index 6ab9c17..c29bce0 100644
--- a/src/test-provider/test-item-data.ts
+++ b/src/test-provider/test-item-data.ts
@@ -798,10 +798,7 @@ export class TestData extends TestResultData {
   }
 
   private getTestNamePattern(): TestNamePattern {
-    if (isDataNode(this.node)) {
-      return { value: this.node.fullName, exactMatch: true };
-    }
-    return { value: this.node.fullName, exactMatch: false };
+    return { value: this.node.fullName, exactMatch: isDataNode(this.node) || false };
   }
 
   getJestRunRequest(options?: ScheduleTestOptions): JestExtRequestType {



Test patch:
diff --git a/tests/helpers.test.ts b/tests/helpers.test.ts
index ceac93c..8e6cb57 100644
--- a/tests/helpers.test.ts
+++ b/tests/helpers.test.ts
@@ -148,18 +148,26 @@ describe('ModuleHelpers', () => {
 
 describe('escapeRegExp', () => {
   it.each`
-    str                                        | expected
-    ${'no special char'}                       | ${'no special char'}
-    ${'with (a)'}                              | ${'with \\(a\\)'}
-    ${'with {} and $sign'}                     | ${'with \\{\\} and \\$sign'}
-    ${'with []'}                               | ${'with \\[\\]'}
-    ${{ value: 'with []', exactMatch: true }}  | ${'with \\[\\]$'}
-    ${{ value: 'with []', exactMatch: false }} | ${'with \\[\\]'}
-    ${{ value: 'with []', isRegExp: true }}    | ${'with []'}
-    ${{ value: 'with []', isRegExp: false }}   | ${'with \\[\\]'}
+    case  | str                                            | expected
+    ${1}  | ${'no special char'}                           | ${'no special char'}
+    ${2}  | ${'with (a)'}                                  | ${'with \\(a\\)'}
+    ${3}  | ${'with {} and $sign'}                         | ${'with \\{\\} and \\$sign'}
+    ${4}  | ${'with []'}                                   | ${'with \\[\\]'}
+    ${5}  | ${{ value: 'with []', exactMatch: true }}      | ${'with \\[\\]$'}
+    ${6}  | ${{ value: 'with []', exactMatch: false }}     | ${'with \\[\\]'}
+    ${7}  | ${{ value: 'with []', isRegExp: true }}        | ${'with []'}
+    ${8}  | ${{ value: 'with []', isRegExp: false }}       | ${'with \\[\\]'}
+    ${9}  | ${'with\nnewline'}                             | ${'with\\nnewline'}
+    ${10} | ${'with\r\nnewline'}                           | ${'with\\nnewline'}
+    ${11} | ${{ value: 'with\nnewline', isRegExp: false }} | ${'with\\nnewline'}
+    ${12} | ${{ value: 'with\nnewline', isRegExp: true }}  | ${'with\\nnewline'}
   `('escapeRegExp: $str', ({ str, expected }) => {
     expect(escapeRegExp(str)).toEqual(expected);
   });
+  it('can skip newline handling', () => {
+    expect(escapeRegExp('with\nnewline', true)).toEqual('with\\nnewline');
+    expect(escapeRegExp('with\nnewline', false)).toEqual('with\nnewline');
+  });
 });
 describe('testIdString', () => {
   it.each`