Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1513 (2023-01-10 18:41:14)

feat: Add status.name text search
<!--- Provide a general summary of your changes in the Title above -->

# Description

Add and document `status.name` text search



## Motivation and Context

Getting closer to release

## How has this been tested?

- Adding tests
- Running it in test vault

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [x] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
- [x] **Documentation** (prefix: `docs` - improvements to any documentation content)

Internal changes:

- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)

## 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! -->

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [x] My change requires a change to the documentation.
- [x] I have [updated the documentation](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#updating-documentation) accordingly.
- [x] 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.
-->

- [x] My contribution follow this project's [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)



Patch:
diff --git a/docs/getting-started/statuses.md b/docs/getting-started/statuses.md
index 6e44d3a0..32723e67 100644
--- a/docs/getting-started/statuses.md
+++ b/docs/getting-started/statuses.md
@@ -83,7 +83,7 @@ Your choice of styling facility will determine which letters and characters you
 Then see [How to set up your custom statuses]({{ site.baseurl }}{% link how-to/set-up-custom-statuses.md %}) for how to set up your custom statuses.
 
 {: .warning }
-Remember to set up your chosen CSS Snippet or Theme before setting up the custom statues.
+Remember to set up your chosen CSS Snippet or Theme before setting up the custom statuses.
 
 ### Minimal supported statuses
 
@@ -156,9 +156,14 @@ We are tracking this in [issue #1486](https://github.com/obsidian-tasks-group/ob
 
 ### Related searches
 
+- `done` - matches tasks with anything except space as the status symbol
+- `not done` - matches task with a space as the status symbol
+- `status.name` text search
+
+For details, see [Filters for Task Statuses]({{ site.baseurl }}{% link queries/filters.md %}#filters-for-task-statuses)
+
 {: .info }
-There are not yet any new specific searches for applying custom statuses.
-We envisage adding search filters for `status.name` and `status.symbol` initially.
+We envisage adding `status.symbol`.
 
 ## Related pages
 
diff --git a/docs/queries/filters.md b/docs/queries/filters.md
index 36b14d98..44e088fd 100644
--- a/docs/queries/filters.md
+++ b/docs/queries/filters.md
@@ -181,6 +181,34 @@ because the tasks starts before tomorrow. Only one of the dates needs to match.
 
 > `no happens date` and `has happens date` were introduced in Tasks 1.7.0.
 
+## Filters for Task Statuses
+
+### Status
+
+- `done` - matches tasks with anything except space as the status symbol
+- `not done` - matches task with a space as the status symbol
+
+### Status Name
+
+- `status.name (includes|does not include) <string>`
+  - Matches case-insensitive (disregards capitalization).
+- `status.name (regex matches|regex does not match) /<JavaScript-style Regex>/`
+  - Does regular expression match (case-sensitive by default).
+  - Essential reading: [Regular Expression Searches]({{ site.baseurl }}{% link queries/regular-expressions.md %}).
+
+> `status.name` text searching was introduced in Tasks X.Y.Z.
+
+For more information, including adding your own customised statuses, see [Statuses]({{ site.baseurl }}{% link getting-started/statuses.md %}).
+
+### Status Examples
+
+Find any tasks that have status symbols you have not yet added to your Tasks settings:
+
+    ```tasks
+    status.name includes unknown
+    group by path
+    ```
+
 ## Filters for Other Task Properties
 
 As well as the date-related searches above, these filters search other properties in individual tasks.
@@ -256,11 +284,6 @@ For more information, see [Priorities]({{ site.baseurl }}{% link getting-started
 
 > `recurrence` text searching was introduced in Tasks 1.22.0.
 
-### Status
-
-- `done`
-- `not done`
-
 ### Sub-Items
 
 - `exclude sub-items`
diff --git a/docs/quick-reference/index.md b/docs/quick-reference/index.md
index 7f147279..e3ed666e 100644
--- a/docs/quick-reference/index.md
+++ b/docs/quick-reference/index.md
@@ -19,12 +19,13 @@ This table summarizes the filters and other options available inside a `tasks` b
 | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | --------------------- | ---------------------- |
 | `done`<br>`not done`                                                                                                                                                                                | `sort by status`                            | `group by status`     |                        |
 | `done (before, after, on) <date>`<br>`has done date`<br>`no  done date`<br>`done date is invalid`                                                                                                   | `sort by done`                              | `group by done`       | `hide done date`       |
+| `status.name (includes, does not include) <string>`<br>`status.name (regex matches, regex does not match) /regex/i`                                                                                 |                                             |                      |                        |
 | `starts (before, after, on) <date>`<br>`has start date`<br>`no  start date`<br>`start date is invalid`                                                                                              | `sort by start`                             | `group by start`      | `hide start date`      |
 | `scheduled (before, after, on) <date>`<br>`has scheduled date`<br>`no  scheduled date`<br>`scheduled date is invalid`                                                                               | `sort by scheduled`                         | `group by scheduled`  | `hide scheduled date`  |
 | `due (before, after, on) <date>`<br>`has due date`<br>`no  due date`<br>`due date is invalid`                                                                                                       | `sort by due`                               | `group by due`        | `hide due date`        |
 | `happens (before, after, on) <date>`<br>`has happens date`<br>`no  happens date`                                                                                                                    | `sort by happens`                           | `group by happens`    |                        |
 | `is recurring`<br>`is not recurring`                                                                                                                                                                |                                             | `group by recurring`  |                        |
-| `recurrence (includes, does not include) <string>`<br>`recurrence (regex matches, regex does not match) /regex/i`                                                                                                                                                                                                    |                                             | `group by recurrence` | `hide recurrence rule` |
+| `recurrence (includes, does not include) <string>`<br>`recurrence (regex matches, regex does not match) /regex/i`                                                                                   |                                             | `group by recurrence` | `hide recurrence rule` |
 | `priority is (above, below, not)? (low, none, medium, high)`                                                                                                                                        | `sort by priority`                          | `group by priority`   | `hide priority`        |
 |                                                                                                                                                                                                     | `sort by urgency`                           |                       | `show urgency`         |
 | `path (includes, does not include) <path>`<br>`path (regex matches, regex does not match) /regex/i`                                                                                                 | `sort by path`                              | `group by path`       |                        |
diff --git a/src/Query/Filter/StatusNameField.ts b/src/Query/Filter/StatusNameField.ts
new file mode 100644
index 00000000..81168f35
--- /dev/null
+++ b/src/Query/Filter/StatusNameField.ts
@@ -0,0 +1,19 @@
+import type { Task } from '../../Task';
+import { TextField } from './TextField';
+
+/**
+ * A ${@link Field} implementation for searching status.name
+ */
+export class StatusNameField extends TextField {
+    constructor() {
+        super();
+    }
+
+    public fieldName(): string {
+        return 'status.name';
+    }
+
+    value(task: Task): string {
+        return task.status.name;
+    }
+}
diff --git a/src/Query/FilterParser.ts b/src/Query/FilterParser.ts
index 57b74b44..09f7a205 100644
--- a/src/Query/FilterParser.ts
+++ b/src/Query/FilterParser.ts
@@ -14,8 +14,9 @@ import { TagsField } from './Filter/TagsField';
 import { BooleanField } from './Filter/BooleanField';
 import { FilenameField } from './Filter/FilenameField';
 import { UrgencyField } from './Filter/UrgencyField';
-import { RecurrenceField } from './Filter/RecurrenceField';
+import { StatusNameField } from './Filter/StatusNameField';
 
+import { RecurrenceField } from './Filter/RecurrenceField';
 import type { FilterOrErrorMessage } from './Filter/Filter';
 import type { Sorter } from './Sorter';
 
@@ -37,6 +38,7 @@ const fieldCreators = [
     () => new FilenameField(),
     () => new UrgencyField(),
     () => new RecurrenceField(),
+    () => new StatusNameField(),
 ];
 
 export function parseFilter(filterString: string): FilterOrErrorMessage | null {


Test patch:
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index ae149e96..24baa220 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -68,6 +68,7 @@ describe('Query parsing', () => {
         'starts after 2021-12-27',
         'starts before 2021-12-27',
         'starts on 2021-12-27',
+        'status.name includes cancelled',
         'tag does not include #sometag',
         'tag does not include sometag',
         'tag includes #sometag',
diff --git a/tests/Query/Filter/StatusNameField.test.ts b/tests/Query/Filter/StatusNameField.test.ts
new file mode 100644
index 00000000..5dc04ee7
--- /dev/null
+++ b/tests/Query/Filter/StatusNameField.test.ts
@@ -0,0 +1,32 @@
+import { StatusNameField } from '../../../src/Query/Filter/StatusNameField';
+import * as TestHelpers from '../../TestHelpers';
+import { toBeValid, toMatchTaskFromLine } from '../../CustomMatchers/CustomMatchersForFilters';
+
+expect.extend({
+    toBeValid,
+    toMatchTaskFromLine,
+});
+
+describe('status.name', () => {
+    it('value', () => {
+        // Arrange
+        const filter = new StatusNameField();
+
+        // Assert
+        expect(filter.value(TestHelpers.fromLine({ line: '- [ ] Xxx' }))).toStrictEqual('Todo');
+        expect(filter.value(TestHelpers.fromLine({ line: '- [/] Xxx' }))).toStrictEqual('In Progress');
+        expect(filter.value(TestHelpers.fromLine({ line: '- [x] Xxx' }))).toStrictEqual('Done');
+        expect(filter.value(TestHelpers.fromLine({ line: '- [-] Xxx' }))).toStrictEqual('Cancelled');
+        expect(filter.value(TestHelpers.fromLine({ line: '- [%] Xxx' }))).toStrictEqual('Unknown');
+    });
+
+    it('status.name includes', () => {
+        // Arrange
+        const filter = new StatusNameField().createFilterOrErrorMessage('status.name includes todo');
+
+        // Assert
+        expect(filter).toBeValid();
+        expect(filter).toMatchTaskFromLine('- [ ] Xxx');
+        expect(filter).not.toMatchTaskFromLine('- [x] Xxx');
+    });
+});