Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1051 (2022-08-19 20:57:06)

docs: Finish docs and tests for regex searches
<!--- Provide a general summary of your changes in the Title above -->

# Description

Many improvements to documentation of regex searches, and some tests added, in order to understand behaviour before writing the docs.

Too many steps to list. Please see individual commits.

## Motivation and Context

Resolves all remaining conversations in #1015.

## How has this been tested?

- By adding and running tests
- By viewing the docs locally


## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Internal changes:

- [x] **Refactor** (prefix: `refactor` - non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)
- [x] **Infrastructure** (prefix: `chore` - examples include GitHub Actions, issue templates)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My code follows the code style of this project and passes `yarn run lint`.
- [ ] My change requires a change to the documentation.
- [ ] I have [updated the documentation](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#updating-documentation) accordingly.
- [ ] My change has adequate [Unit Test coverage](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#maintaining-the-tests).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [ ] My contribution follow this project's [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)
- [ ] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)


claremacrae: @AnnaKornfeldSimpson and @elight for info...

But I'm not sure that I want any feedback!!! 😉 

Anything seriously wrong, please do let me know 😁 

Patch:
diff --git a/docs/queries/examples.md b/docs/queries/examples.md
index 6a425b04..ab633d7a 100644
--- a/docs/queries/examples.md
+++ b/docs/queries/examples.md
@@ -95,3 +95,12 @@ All open tasks that are due today or earlier, sorted by due date, then grouped t
     sort by due
     group by folder
     ```
+
+---
+
+All open tasks that begin with a time stamp in `HH:mm` format, followed by any white space character:
+
+    ```tasks
+    not done
+    description regex matches /^[012][0-9]:[0-5][0-9]\s/
+    ```
diff --git a/docs/queries/filters.md b/docs/queries/filters.md
index 354e835a..35cf4931 100644
--- a/docs/queries/filters.md
+++ b/docs/queries/filters.md
@@ -140,6 +140,21 @@ As well as the date-related searches above, these filters search other propertie
 
 > `regex matches` and `regex does not match` were introduced in Tasks 1.12.0.
 
+For precise searches, it may help to know that `description`:
+
+- first removes all each task's signifier emojis and their values,
+- then removes any global filter,
+- then removes an trailing spaces
+- and then searches the remaining text
+
+For example:
+
+| Global Filter    | Task line                                                                | Text searched by `description`   |
+| ---------------- | ------------------------------------------------------------------------ | -------------------------------- |
+| No global filter | `'- [ ] Do stuff  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag '`               | `'Do stuff #tag1 #tag2/sub-tag'` |
+| `#task`          | `'- [ ] #task Do stuff  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag '`         | `'Do stuff #tag1 #tag2/sub-tag'` |
+| `global-filter`  | `'- [ ] global-filter Do stuff  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag '` | `'Do stuff #tag1 #tag2/sub-tag'` |
+
 ### Priority
 
 - `priority is (above|below)? (low|none|medium|high)`
@@ -209,7 +224,7 @@ These filters allow searching for tasks in particular files and sections of file
   - `does not include` will match a task that does not have a preceding heading in its file.
   - Matches case-insensitive (disregards capitalization).
 - `heading (regex matches|regex does not match) /<JavaScript-style Regex>/`
-  - Whether or not the heading preceding the task includes the given regular expression.
+  - Whether or not the heading preceding the task includes the given regular expression (case-sensitive by default).
   - Always tries to match the closest heading above the task, regardless of heading level.
   - `regex does not match` will match a task that does not have a preceding heading in its file.
   - Essential reading: [Regular Expression Searches]({{ site.baseurl }}{% link queries/regular-expressions.md %}).
diff --git a/docs/queries/regular-expressions.md b/docs/queries/regular-expressions.md
index 1f579df1..8a8de63d 100644
--- a/docs/queries/regular-expressions.md
+++ b/docs/queries/regular-expressions.md
@@ -78,6 +78,14 @@ description regex matches /pc_abigail|pc_edwina|at_work/i
 - Note that `tags` searches do not yet support regex searches.
   - As a workaround, search `description` instead.
 
+## Escaping special characters
+
+To search for any of the characters `[ \ ^ $ . | ? * + ( ) /` literally in Tasks, you should put a `\` character before each of them.
+
+This is called 'escaping'. See [Escaping, special characters](https://javascript.info/regexp-escaping).
+
+See the next section for the meaning of some of these characters.
+
 ## Special characters
 
 If using regex searches, it is important to be aware of the available special characters for several reasons:
@@ -85,20 +93,39 @@ If using regex searches, it is important to be aware of the available special ch
 1. They enable complex queries to written in simple ways
 2. They can cause confusing results or broken searches, if not "escaped" in the search.
 
-Here are a few examples of the [many special characters](https://www.rexegg.com/regex-quickstart.html):
+Here are a few examples of the [many special characters](https://javascript.info/regexp-escaping):
 
 - `.` matches any character
-- `^` matches the start of the string (but when `[^inside brackets]`, it means "not")
-- `$` matches the end of the string
+- `[...]` means search for any of the characters in the square brackets.
+  - For example, `[aeiou]` will match any of an `a`, `e`, `i`, `o` or `u`.
+  - See [Sets and ranges \[...\]](https://javascript.info/regexp-character-sets-and-ranges)
+- Start and end
+  - `^` matches the start of the string (but when `[^inside brackets]`, it means "not")
+  - `$` matches the end of the string
+  - See [Anchors: string start ^ and end $](https://javascript.info/regexp-anchors)
+- `|` is an `OR` in regular expressions
+  - See [Alternation (OR) |](https://javascript.info/regexp-alternation)
 - `\` adds special meaning to some characters. For example:
   - `\d` matches one digit, from 0 to 9
   - `\D` matches character that is not a digit
+  - See [Character classes](https://javascript.info/regexp-character-classes)
+
+For a thorough, clear introduction to all the options, see [Regular expressions](https://javascript.info/regular-expressions) at JavaScript.info.
 
 ## Important links
 
+Learning resources:
+
+- [Regular expressions](https://javascript.info/regular-expressions) at JavaScript.info
 - [Regex Tutorial](https://regexone.com/)
 - [Regex Cheat Sheet](https://www.rexegg.com/regex-quickstart.html)
+
+Online tools for experimenting with - and testing - regular expressions:
+
 - [Regex Testing Tool: regex101](https://regex101.com/): Select the flavor 'ECMAScript (JavaScript)'
+
+Implementation details:
+
 - Implemented using [JavaScript's RegExp implementation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
 - Supports [JavaScript RegExp Flags](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags), although not all of them are relevant in this context.
 
@@ -109,22 +136,103 @@ Please be aware of the following limitations in Tasks' implementation of regular
 - The single error message `Tasks query: cannot parse regex (description); check your leading and trailing slashes for your query` may mean any of:
   - The opening or closing `/` is missing from the query.
   - The regular expression is not valid, for example `description regex matches /[123/`.
+  - Logged in [#1038](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1038) and [#1039](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1039)
 - No error when part of the pattern is lost, for example because unescaped slashes are used inside the pattern.
   - For example, `path regex matches /a/b/c/d/` actually searches for `path regex matches /a/`.
   - In this case, the query should be `path regex matches /a\/b\/c\/d/`.
+  - Logged in [#1037](https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1037)
 - Illegal flags are ignored.
   - For example, the query `description regex matches /CASE/&` should give an error that `&` (and similar) are unrecognised flags.
 - The `tag` or `tags` instruction does not yet support regular expression searches.
+  - Logged in [#1040](https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/1040)
 - [Lookahead and Lookbehind](https://www.regular-expressions.info/lookaround.html) searches are untested, and are presumed not to work on Apple mobile devices, or to cause serious performance problems with slow searches.
 
 ## Regular expression examples
 
-Example searches:
+Below are some example regex searches, to give some ideas of what can be done.
+
+There are some more examples in the [Tasks-Demo sample vault](https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/resources/sample_vaults/Tasks-Demo), in the file [Regular Expression Searches](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/resources/sample_vaults/Tasks-Demo/Filters/Regular%20Expression%20Searches.md).
+
+### Searching the start of a field
+
+Find tasks whose description begins with Log, exact capitalisation:
 
 ```text
-# Find tasks whose description begins with Log, exact capitalisation
 description regex matches /^Log/
+```
+
+---
 
-# Find tasks whose description begins with Log, ignoring capitalisation
+Find tasks whose description begins with Log, ignoring capitalisation
+
+```text
 description regex matches /^Log/i
 ```
+
+### Finding tasks that are waiting
+
+I want to find tasks that are waiting for something else. But 'waiting' can be spelled in several different ways:
+
+```text
+description regex matches /waiting|waits|wartet/i
+```
+
+### Finding times
+
+Find tasks containing a time in the description - simple version. This matches invalid times, such as `99:99`, as `\d` means 'any digit'.
+
+```text
+description regex matches /\d\d:\d\d/
+```
+
+---
+
+Find tasks containing a time in the description. This is more precise than the previous example, thanks to specifying which digits are allowed in each position.
+
+```text
+description regex matches /[012][0-9]:[0-5][0-9]/
+```
+
+### Finding sub-tags
+
+Currently `tag` and `tags` searches do not yet support regular expressions. Therefore, for precise searching of tags, use  `description` instead.
+
+Suppose you wanted to search for tags of this form: `#tag/subtag3/subsubtag5`, where the `3` and the `5` are allowed to be any single digit.
+
+- We can use either `[0-9]` or `\d` to match a single digit.
+- To find a sub-tag, any `/` characters must be 'escaped' to prevent them truncating the rest of the search pattern.
+
+Escaping the `/` leads us to this instruction, which we have made case-insensitive to find capitalised tags too:
+
+```text
+description regex matches /#tag\/subtag[0-9]\/subsubtag[0-9]/i
+```
+
+### Finding short tags
+
+Currently `tag` and `tags` searches do not yet support regular expressions. Therefore, for precise searching of tags, use  `description` instead.
+
+Suppose you wanted to search for tasks with a very short tag in: `#t`, and to not match tags line `#task` and `#t/subtag`.
+
+The most general query is:
+
+```text
+(description regex matches /#t\s/i) OR (description regex matches /#t$/i)
+```
+
+We have made it case-insensitive to find capitalised tags too.
+
+The Boolean `OR` allows us to search for two different patterns, for a thorough search:
+
+- `description regex matches /#t\s/i`
+  - Matches `#t` or `#T`, followed by any white-space character. This might be a literal space, or it might be a tab, for example.
+  - It won't be a newline character though, as task descriptions are always exactly one line, with no newline character at the end.
+  - For example, this will match:
+    - `- [ ] #t Do stuff`
+    - `- [ ] Do #t stuff`
+  - But it will not match:
+    - `- [ ] Do stuff #t`
+- `description regex matches /#t$/i`
+  - Matches `#t` or `#T` at the very end of the task line (after all signifiers and trailing white space has been removed)
+  - For example, this will match:
+    - `- [ ] Do stuff #t`
diff --git a/resources/sample_vaults/Tasks-Demo/Filters/Regular Expression Searches.md b/resources/sample_vaults/Tasks-Demo/Filters/Regular Expression Searches.md
index bcf5ea20..56e7d5ad 100644
--- a/resources/sample_vaults/Tasks-Demo/Filters/Regular Expression Searches.md
+++ b/resources/sample_vaults/Tasks-Demo/Filters/Regular Expression Searches.md
@@ -1,5 +1,10 @@
 # Regular Expression Searches
 
+This file contains a few examples that were useful when testing
+the feature to search for text using regular expressions.
+
+Full documentation is available: see [Regular Expressions](https://obsidian-tasks-group.github.io/obsidian-tasks/queries/regular-expressions/).
+
 ## Sample Tasks
 
 - [ ] #task Do thing 1 #context/pc_abigail
diff --git a/src/Query/Filter/DescriptionField.ts b/src/Query/Filter/DescriptionField.ts
index d31bb931..81df4772 100644
--- a/src/Query/Filter/DescriptionField.ts
+++ b/src/Query/Filter/DescriptionField.ts
@@ -15,10 +15,12 @@ export class DescriptionField extends TextField {
 
     /**
      * Return the task's description, with any global tag removed
+     *
+     * Promoted to public, to enable testing.
      * @param task
-     * @protected
+     * @public
      */
-    protected value(task: Task): string {
+    public value(task: Task): string {
         // Remove global filter from description match if present.
         // This is necessary to match only on the content of the task, not
         // the global filter.


Test patch:
diff --git a/tests/Query/Filter/DescriptionField.test.ts b/tests/Query/Filter/DescriptionField.test.ts
index feefc2cd..59d2a54c 100644
--- a/tests/Query/Filter/DescriptionField.test.ts
+++ b/tests/Query/Filter/DescriptionField.test.ts
@@ -1,10 +1,17 @@
+/**
+ * @jest-environment jsdom
+ */
+import moment from 'moment';
 import { DescriptionField } from '../../../src/Query/Filter/DescriptionField';
 import { getSettings, updateSettings } from '../../../src/config/Settings';
 import { testTaskFilter } from '../../TestingTools/FilterTestHelpers';
 import { fromLine } from '../../TestHelpers';
 import type { FilterOrErrorMessage } from '../../../src/Query/Filter/Filter';
+import { BooleanField } from '../../../src/Query/Filter/BooleanField';
 import { toMatchTaskFromLine } from '../../CustomMatchers/CustomMatchersForFilters';
 
+window.moment = moment;
+
 function testDescriptionFilter(
     filter: FilterOrErrorMessage,
     line: string,
@@ -20,6 +27,63 @@ expect.extend({
     toMatchTaskFromLine,
 });
 
+describe('description should strip signifiers, some duplicate spaces and trailing spaces', () => {
+    const field = new DescriptionField();
+    it('without global filter - all tags included', () => {
+        // Arrange
+        const task = fromLine({
+            line: '- [ ]   Initial  description  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag ',
+        });
+
+        // Act, Assert
+        // Multiple spaces before and in middle of description are retained.
+        // Multiple spaces in middle of tags and signifiers are removed.
+        // Signifiers are removed (priority, due date etc)
+        // Trailing spaces are removed.
+        expect(field.value(task)).toStrictEqual(
+            'Initial  description #tag1 #tag2/sub-tag',
+        );
+    });
+
+    it('with tag as global filter - all tags included', () => {
+        // Arrange
+        const originalSettings = getSettings();
+        updateSettings({ globalFilter: '#task' });
+
+        const task = fromLine({
+            line: '- [ ] #task Initial  description  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag ',
+        });
+
+        // Act, Assert
+        // Global filter tag is removed:
+        expect(field.value(task)).toStrictEqual(
+            'Initial  description #tag1 #tag2/sub-tag',
+        );
+
+        // Cleanup
+        updateSettings(originalSettings);
+    });
+
+    it('with non-tag as global filter - all tags included', () => {
+        // Arrange
+        const originalSettings = getSettings();
+        updateSettings({ globalFilter: 'global-filter' });
+
+        const task = fromLine({
+            line: '- [ ] global-filter Initial  description  ⏫  #tag1 ✅ 2022-08-12 #tag2/sub-tag ',
+        });
+
+        // Act, Assert
+        // Global filter tag is removed:
+        expect(field.value(task)).toStrictEqual(
+            'Initial  description #tag1 #tag2/sub-tag',
+        );
+
+        // Cleanup
+        updateSettings(originalSettings);
+    });
+});
+
 describe('description', () => {
     it('ignores the global filter when filtering', () => {
         // Arrange
@@ -99,3 +163,125 @@ describe('description', () => {
         );
     });
 });
+
+describe('search description for time stamps', () => {
+    it('should find a time stamp in the description - simple version', () => {
+        // Arrange
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            String.raw`description regex matches /\d\d:\d\d/`,
+        );
+
+        // Assert
+        expect(filter).toMatchTaskFromLine('- [ ] Do me at 23:59');
+        expect(filter).toMatchTaskFromLine('- [ ] Do me at 00:01');
+        expect(filter).toMatchTaskFromLine('- [ ] Do me at 99:99');
+    });
+
+    it('should find a time stamp in the description - more precise version', () => {
+        // Arrange
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            'description regex matches /[012][0-9]:[0-5][0-9]/',
+        );
+
+        // Assert
+        expect(filter).toMatchTaskFromLine('- [ ] Do me at 23:59');
+        expect(filter).toMatchTaskFromLine('- [ ] Do me at 00:01');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do me at 99:99');
+    });
+});
+
+describe('search description for short tags, excluding sub-tags', () => {
+    it('should search for a short tag anywhere in the line except the end', () => {
+        // Arrange
+        // \s is a whitespace character.
+        // In regular text, it would match a newline character, so would find
+        // text at the end of lines.
+        // However, task descriptions do not have end-of-line characters,
+        // so it does not match a tag at the end of the line.
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            String.raw`description regex matches /#t\s/i`,
+        );
+
+        // Assert
+        expect(filter).toMatchTaskFromLine('- [ ] #t Do stuff');
+        expect(filter).toMatchTaskFromLine('- [ ] Do #t stuff');
+
+        // Confirm that tags at end of line are not found. (See comments above.)
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do stuff #t');
+
+        // Confirm that tags with sub-tags are not found:
+        expect(filter).not.toMatchTaskFromLine('- [ ] #t/b Do stuff');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do #t/b stuff');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do stuff #t/b');
+    });
+
+    it('should search for a short tag at the end of the task', () => {
+        // Arrange
+        // $ is an end-of-input character.
+        // So this search will find the given tag at the end of any task description
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            'description regex matches /#t$/i',
+        );
+
+        // Assert
+        expect(filter).toMatchTaskFromLine('- [ ] Do stuff #t');
+
+        expect(filter).not.toMatchTaskFromLine('- [ ] #t Do stuff');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do #t stuff');
+
+        // Confirm that tags with sub-tags are not found:
+        expect(filter).not.toMatchTaskFromLine('- [ ] #t/b Do stuff');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do #t/b stuff');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do stuff #t/b');
+    });
+
+    it('should search for a short tag anywhere', () => {
+        // Arrange
+        const filter = new BooleanField().createFilterOrErrorMessage(
+            String.raw`(description regex matches /#t\s/i) OR (description regex matches /#t$/i)`,
+        );
+
+        // Assert
+        expect(filter).toMatchTaskFromLine('- [ ] #t Do stuff');
+        expect(filter).toMatchTaskFromLine('- [ ] Do #t stuff');
+        expect(filter).toMatchTaskFromLine('- [ ] Do stuff #t');
+
+        // Confirm that tags with sub-tags are not found:
+        expect(filter).not.toMatchTaskFromLine('- [ ] #t/b Do stuff');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do #t/b stuff');
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do stuff #t/b');
+    });
+});
+
+describe('search description for sub-tags', () => {
+    it('should search for a short tag anywhere', () => {
+        // Arrange
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            String.raw`description regex matches /#tag\/subtag[0-9]\/subsubtag[0-9]/i`,
+        );
+
+        // Assert
+        expect(filter).toMatchTaskFromLine(
+            '- [ ] Do stuff #tag/subtag3/subsubtag5',
+        );
+        expect(filter).not.toMatchTaskFromLine('- [ ] Do stuff #tag');
+    });
+});
+
+describe('search description for Alternation (OR)', () => {
+    it('should search for one of several spellings of waiting', () => {
+        // Regex equivalent of:
+        //      (description includes waiting) OR (description includes waits) OR (description includes wartet)
+        // See https://obsidian-tasks-group.github.io/obsidian-tasks/queries/combining-filters/#finding-tasks-that-are-waiting
+
+        // Arrange
+        const filter = new DescriptionField().createFilterOrErrorMessage(
+            String.raw`description regex matches /waiting|waits|wartet/i`,
+        );
+
+        // Assert
+        expect(filter).toMatchTaskFromLine('- [ ] Do stuff waiting');
+        expect(filter).toMatchTaskFromLine('- [ ] Do stuff waits');
+        expect(filter).toMatchTaskFromLine('- [ ] Do stuff wartet');
+    });
+});
diff --git a/tests/Query/Filter/HeadingField.test.ts b/tests/Query/Filter/HeadingField.test.ts
index 9532dcda..094bd483 100644
--- a/tests/Query/Filter/HeadingField.test.ts
+++ b/tests/Query/Filter/HeadingField.test.ts
@@ -52,7 +52,7 @@ describe('heading', () => {
     it('by heading (regex matches - case sensitive)', () => {
         // Arrange
         const filter = new HeadingField().createFilterOrErrorMessage(
-            'heading regex matches /[Ii]nteresting Head.ng/',
+            String.raw`heading regex matches /[Ii]nteresting Head.ng/`,
         );
 
         // Act, Assert
@@ -65,7 +65,7 @@ describe('heading', () => {
     it('by heading (regex does not match - case in-sensitive)', () => {
         // Arrange
         const filter = new HeadingField().createFilterOrErrorMessage(
-            'heading regex does not match /[Ii]nteresting Head.ng/i',
+            String.raw`heading regex does not match /[Ii]nteresting Head.ng/i`,
         );
 
         // Act, Assert
diff --git a/tests/Query/Filter/PathField.test.ts b/tests/Query/Filter/PathField.test.ts
index 0fc9b871..af1bc96e 100644
--- a/tests/Query/Filter/PathField.test.ts
+++ b/tests/Query/Filter/PathField.test.ts
@@ -53,7 +53,7 @@ describe('path', () => {
     it('by path (regex matches)', () => {
         // Arrange
         const filter = new PathField().createFilterOrErrorMessage(
-            'path regex matches /w.bble/',
+            String.raw`path regex matches /w.bble/`,
         );
 
         // Assert
@@ -68,7 +68,7 @@ describe('path', () => {
     it('by path (regex matches) with flags', () => {
         // Arrange
         const filter = new PathField().createFilterOrErrorMessage(
-            'path regex matches /w.bble/i',
+            String.raw`path regex matches /w.bble/i`,
         );
 
         // Assert
@@ -83,7 +83,7 @@ describe('path', () => {
     it('by path (regex does not match)', () => {
         // Arrange
         const filter = new PathField().createFilterOrErrorMessage(
-            'path regex does not match /w.bble/',
+            String.raw`path regex does not match /w.bble/`,
         );
 
         // Assert
@@ -99,7 +99,7 @@ describe('path', () => {
 describe('invalid unescaped slash should give helpful error text and not search', () => {
     const filterWithUnescapedSlashes =
         new PathField().createFilterOrErrorMessage(
-            'path regex matches /a/b/c/d/',
+            String.raw`path regex matches /a/b/c/d/`,
         );
 
     // This test demonstrates the issue logged in