Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2634 (2024-02-01 21:53:58)

feat: Add consistent searches for 'blocked' and 'blocked'
<!--- Provide a general summary of your changes in the Title above -->

# Description

This all arose from exploratory testing done in conjunction with @ilandikov on Tuesday.

There are now 4 instructions:

- `is blocking`
- `is not blocking`
- `is blocked`
- `is not blocked`

Both `is blocking` and `is blocked` only apply if the tasks at both ends are `TODO` or `IN_PROGRESS` status type.

`DONE`, `CANCELLED` and `NON_TASK` status types never count as `blocking` or `blocked`.

I've updated the dependencies sample file in the vault with more realistic real-world searches, and uses of all the above instructions.

## Motivation and Context

Simplify searches for tasks by dependency. And make `blocking` and `blocked` concepts both handle task status consistently.

## How has this been tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

- unit tests
- approval tests
- manual exploratory testing

## 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 **for users**)
- [x] **Sample vault** (prefix: `vault` - improvements to the [Tasks-Demo sample vault](https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/resources/sample_vaults/Tasks-Demo))

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://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://publish.obsidian.md/tasks-contributing) 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://publish.obsidian.md/tasks-contributing)
- [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/Queries/Filters.md b/docs/Queries/Filters.md
index efedcc4f..0a98f898 100644
--- a/docs/Queries/Filters.md
+++ b/docs/Queries/Filters.md
@@ -453,8 +453,76 @@ Find any tasks that have status symbols you have not yet added to your Tasks set
 
 ## Filters for Task Dependencies
 
+At a high level, task dependencies define the order in which you want to work on a set of tasks. You can read more about them in [[Task Dependencies]].
+
+### Blocking Tasks
+
+- `is blocking`
+  - This shows tasks that you probably want to do first, as they are preventing other tasks from being done.
+- `is not blocking`
+  - This shows tasks that are not preventing others from being done, so perhaps may be considered as lower priority.
+  - This would typically be used with `not done`.
+
+A task is treated as `blocking` if:
+
+- it has an `id` value,
+- at least one other task in the vault has that `id` value in its `blockedBy` list,
+- both tasks have status type `TODO` or `IN_PROGRESS`.
+
+For example:
+
+```text
+- [ ] I am blocking 🆔 12345
+- [ ] I am not blocking ⛔️ 12345
+```
+
+Note also:
+
+- Only direct dependencies are considered.
+- Tasks with status type `DONE`, `CANCELLED` or `NON_TASK` are never treated as `blocked`.
+
+For more information, see [[Task Dependencies]].
+
+> [!released]
+>
+> - `is blocking` and `is not blocking` were introduced in Tasks X.Y.Z.
+
+### Blocked Tasks
+
+- `is blocked`
+  - This shows tasks you cannot currently do, as they are waiting for another task to be completed.
+- `is not blocked`
+  - This shows tasks that are not waiting for any other tasks to be completed.
+  - This would typically be used with `not done`.
+
+A task is treated as `blocked` if:
+
+- it has one or more `blockedBy` values,
+- its `blockedBy` list includes the id any tasks in the vault,
+- both tasks have status type `TODO` or `IN_PROGRESS`.
+
+For example:
+
+```text
+- [ ] I am not blocked 🆔 12345
+- [ ] I am blocked ⛔️ 12345
+```
+
+Note also:
+
+- Only direct dependencies are considered.
+- Tasks with status type `DONE`, `CANCELLED` or `NON_TASK` are never treated as `blocked`.
+
+For more information, see [[Task Dependencies]].
+
+> [!released]
+>
+> - `is blocked` and `is not blocked` were introduced in Tasks X.Y.Z.
+
 ### Id
 
+The `id` field adds an identifier to a task, so that other tasks may be marked as `blockedBy` that task.
+
 - `has id`
 - `no id`
 - `id (includes|does not include) <string>`
@@ -473,6 +541,8 @@ Since Tasks X.Y.Z, **[[Custom Filters|custom filtering]] by Id** is now possible
 
 ### Blocked By
 
+The `blockedBy` field allows a task to be marked as depending on the `id` of one or more other tasks. Multiple `id` values are separated by commas (`,`) with no spaces.
+
 - `has blocked by`
 - `no blocked by`
 
diff --git a/docs/Quick Reference.md b/docs/Quick Reference.md
index 8264bef4..a99693f5 100644
--- a/docs/Quick Reference.md
+++ b/docs/Quick Reference.md
@@ -20,9 +20,9 @@ This table summarizes the filters and other options available inside a `tasks` b
 |                                                                                                                                                                                                                                                                                                                                                      |                                             |                        |                        | `task.status.nextSymbol`                                                                                                       |
 | **[[Task Dependencies]]**                                                                                                                                                                                                                                                                                                                            |                                             |                        |                        |                                                                                                                                |
 | `id (includes, does not include) <string>`<br>`id (regex matches, regex does not match) /regex/i`<br>`has id`<br>`no id`                                                                                                                                                                                                                             | `sort by id`                                | `group by id`          | `hide id`              | `task.id`                                                                                                                      |
-| `has blocked by`<br>`no blocked by`                                                                                                                                                                                                                                                                                                                  |                                             |                        | `hide blocked by`      | `task.blockedBy`                                                                                                               |
-| `is not blocked`                                                                                                                                                                                                                                                                                                                                     |                                             |                        |                        |                                                                                                                                |
-| `is blocking`                                                                                                                                                                                                                                                                                                                                        |                                             |                        |                        |                                                                                                                                |
+| `has blocked by`<br>`no blocked by`                                                                                                                                                                                                                                                                                                                  |                                             |                        | `hide depends on`      | `task.blockedBy`                                                                                                               |
+| `is blocked`<br>`is not blocked`                                                                                                                                                                                                                                                                                                                     |                                             |                        |                        |                                                                                                                                |
+| `is blocking`<br>`is not blocking`                                                                                                                                                                                                                                                                                                                   |                                             |                        |                        |                                                                                                                                |
 | **[[Filters#Filters for Dates in Tasks\|Dates]]**                                                                                                                                                                                                                                                                                                    |                                             |                        |                        |                                                                                                                                |
 | `done (on, before, after, on or before, on or after) <date>`<br>`done (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has done date`<br>`no done date`<br>`done date is invalid`                          | `sort by done`                              | `group by done`        | `hide done date`       | `task.done`                                                                                                                    |
 | `created (on, before, after, on or before, on or after) <date>`<br>`created (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has created date`<br>`no created date`<br>`created date is invalid`           | `sort by created`                           | `group by created`     | `hide created date`    | `task.created`                                                                                                                 |
diff --git a/src/Query/Filter/BlockingField.ts b/src/Query/Filter/BlockingField.ts
index ba54c31a..f085af74 100644
--- a/src/Query/Filter/BlockingField.ts
+++ b/src/Query/Filter/BlockingField.ts
@@ -5,24 +5,19 @@ export class BlockingField extends FilterInstructionsBasedField {
     constructor() {
         super();
         this._filters.add('is blocking', (task, searchInfo: SearchInfo) => {
-            if (task.id === '') return false;
-
-            return searchInfo.allTasks.some((cacheTask) => {
-                return cacheTask.blockedBy.includes(task.id);
-            });
+            return task.isBlocking(searchInfo.allTasks);
         });
-        this._filters.add('is not blocked', (task, searchInfo: SearchInfo) => {
-            if (task.blockedBy.length === 0) return true;
-
-            for (const depId of task.blockedBy) {
-                const depTask = searchInfo.allTasks.find((task) => task.id === depId);
 
-                if (!depTask) continue;
+        this._filters.add('is not blocking', (task, searchInfo: SearchInfo) => {
+            return !task.isBlocking(searchInfo.allTasks);
+        });
 
-                if (!depTask.status.isCompleted()) return false;
-            }
+        this._filters.add('is blocked', (task, searchInfo: SearchInfo) => {
+            return task.isBlocked(searchInfo.allTasks);
+        });
 
-            return true;
+        this._filters.add('is not blocked', (task, searchInfo: SearchInfo) => {
+            return !task.isBlocked(searchInfo.allTasks);
         });
     }
 
diff --git a/src/Task/Task.ts b/src/Task/Task.ts
index ec8fcc4d..1889b7cb 100644
--- a/src/Task/Task.ts
+++ b/src/Task/Task.ts
@@ -457,7 +457,7 @@ export class Task {
      * Only direct dependencies are considered.
      * @param allTasks - all the tasks in the vault. In custom queries, this is available via query.allTasks.
      */
-    public isBlocked(allTasks: Task[]) {
+    public isBlocked(allTasks: Readonly<Task[]>) {
         if (this.blockedBy.length === 0) {
             return false;
         }
@@ -487,7 +487,7 @@ export class Task {
      * Only direct dependencies are considered.
      * @param allTasks - all the tasks in the vault. In custom queries, this is available via query.allTasks.
      */
-    public isBlocking(allTasks: Task[]) {
+    public isBlocking(allTasks: Readonly<Task[]>) {
         if (this.id === '') {
             return false;
         }


Test patch:
diff --git a/resources/sample_vaults/Tasks-Demo/Manual Testing/Dependencies Samples.md b/resources/sample_vaults/Tasks-Demo/Manual Testing/Dependencies Samples.md
index 4af970ec..223cca42 100644
--- a/resources/sample_vaults/Tasks-Demo/Manual Testing/Dependencies Samples.md
+++ b/resources/sample_vaults/Tasks-Demo/Manual Testing/Dependencies Samples.md
@@ -7,81 +7,127 @@
 - [ ] #task Craft a conclusion 🆔 0wigip ⛔️ mvplec
 - [ ] #task Proofread and edit 🆔 5ti6bf ⛔️ 0wigip
 - [ ] #task Publish the article ⛔️ 5ti6bf
+- [ ] #task Do something on a different project
 
 ---
 
-## Do Next
+## Real-world searches
+
+### Blocking and Not Blocked - Do very soon
+
+```tasks
+is blocking
+is not blocked
+# by definition these are all 'not done'
+
+path includes {{query.file.path}}
+#explain
+```
+
+### Not Blocking and Not Blocked - Do any time
 
 ```tasks
-((is blocking) AND (is not blocked)) OR (is not blocked)
+is not blocking
+is not blocked
+
+# DONE, CANCELLED and NON_TASK are never blocking nor blocked,
+# so we need to exclude those:
 not done
 
 path includes {{query.file.path}}
-explain
+#explain
+```
+
+### Blocked - Cannot yet do
+
+```tasks
+is blocked
+# by definition these are all 'not done'
+
+path includes {{query.file.path}}
+#explain
 ```
 
 ---
 
-## Blocking Tasks
+## Demonstration searches - showing the Blocking instructions
 
-### Blocking Tasks - Any Status
+### `is blocking` - Blocking Tasks - tasks which prevent others from being done
 
 ```tasks
 is blocking
 
 path includes {{query.file.path}}
-explain
+#explain
 ```
 
-### Blocking Tasks - Not Done
+### `is not blocking` - Non-blocking Tasks - Not Done
 
 ```tasks
-is blocking
+is not blocking
 not done
 
 path includes {{query.file.path}}
-explain
+#explain
+```
+
+### `is not blocking` - Non-blocking Tasks - Any Status
+
+```tasks
+is not blocking
+
+path includes {{query.file.path}}
+#explain
 ```
 
 ---
 
-## Blocked Tasks
+## Demonstration searches - showing the Blocked instructions
 
-### Blocked Tasks  - Any Status
+### `is blocked` - Blocked Tasks - tasks which cannot be done yet
 
 ```tasks
-is not blocked
+is blocked
 
 path includes {{query.file.path}}
-explain
+#explain
 ```
 
-### Blocked Tasks  - Not Done
+### `is not blocked` - Non-blocked Tasks - Not Done
 
 ```tasks
 is not blocked
 not done
 
 path includes {{query.file.path}}
-explain
+#explain
+```
+
+### `is not blocked` - Non-blocked Tasks - Any Status
+
+```tasks
+is not blocked
+
+path includes {{query.file.path}}
+#explain
 ```
 
 ---
 
 ## Show/Hide Instructions
 
-### Hide Id
+### `hide id`
 
 ```tasks
 hide id
 path includes {{query.file.path}}
-explain
+#explain
 ```
 
-### Hide blockedBy
+### `hide depends on` ==TODO Rename to blocked by==
 
 ```tasks
 hide depends on
 path includes {{query.file.path}}
-explain
+#explain
 ```
diff --git a/tests/CustomMatchers/CustomMatchersForFilters.ts b/tests/CustomMatchers/CustomMatchersForFilters.ts
index 84d21259..f6a2fba2 100644
--- a/tests/CustomMatchers/CustomMatchersForFilters.ts
+++ b/tests/CustomMatchers/CustomMatchersForFilters.ts
@@ -174,6 +174,10 @@ with filter: "${filter.instruction}"`,
 }
 
 export function toMatchTaskInTaskList(filter: FilterOrErrorMessage, task: Task, allTasks: Task[]) {
+    // Make sure that the task being filtered is actually in allTasks,
+    // to guard against tests passing for the wrong reason:
+    expect(allTasks.includes(task)).toEqual(true);
+
     return toMatchTaskWithSearchInfo(filter, task, SearchInfo.fromAllTasks(allTasks));
 }
 
diff --git a/tests/DocumentationSamples/DependencyDocs/DependencySamples.test.blocking_and_blocked_filters_blocking_and_blocked.approved.md b/tests/DocumentationSamples/DependencyDocs/DependencySamples.test.blocking_and_blocked_filters_blocking_and_blocked.approved.md
new file mode 100644
index 00000000..fb4ab0ca
--- /dev/null
+++ b/tests/DocumentationSamples/DependencyDocs/DependencySamples.test.blocking_and_blocked_filters_blocking_and_blocked.approved.md
@@ -0,0 +1,36 @@
+<!-- placeholder to force blank line before included text -->
+
+| Task | `is blocking` | `is blocked` |
+| ----- | ----- | ----- |
+| `- [ ] No dependency - TODO` | ❌ false | ❌ false |
+| `- [x] No dependency - DONE` | ❌ false | ❌ false |
+| `- [ ] scenario 1 - TODO depends on TODO 🆔 scenario1` | ✅ true | ❌ false |
+| `- [ ] scenario 1 - TODO depends on TODO ⛔️ scenario1` | ❌ false | ✅ true |
+| `- [x] scenario 2 - TODO depends on DONE 🆔 scenario2` | ❌ false | ❌ false |
+| `- [ ] scenario 2 - TODO depends on DONE ⛔️ scenario2` | ❌ false | ❌ false |
+| `- [ ] scenario 3 - DONE depends on TODO 🆔 scenario3` | ❌ false | ❌ false |
+| `- [x] scenario 3 - DONE depends on TODO ⛔️ scenario3` | ❌ false | ❌ false |
+| `- [x] scenario 4 - DONE depends on DONE 🆔 scenario4` | ❌ false | ❌ false |
+| `- [x] scenario 4 - DONE depends on DONE ⛔️ scenario4` | ❌ false | ❌ false |
+| `- [ ] scenario 5 - TODO depends on non-existing ID ⛔️ nosuchid` | ❌ false | ❌ false |
+| `- [ ] scenario 6 - TODO depends on self 🆔 self ⛔️ self` | ✅ true | ✅ true |
+| `- [x] scenario 7 - task with duplicated id - this is DONE                                  - 🆔 scenario7` | ❌ false | ❌ false |
+| `- [ ] scenario 7 - task with duplicated id - this is TODO - and is blocking                - 🆔 scenario7` | ✅ true | ❌ false |
+| `- [ ] scenario 7 - TODO depends on id that is duplicated - ensure all tasks are checked    - ⛔️ scenario7` | ❌ false | ✅ true |
+| `- [ ] scenario 8 - mutually dependant 🆔 scenario8a ⛔️ scenario8b` | ✅ true | ✅ true |
+| `- [ ] scenario 8 - mutually dependant 🆔 scenario8b ⛔️ scenario8a` | ✅ true | ✅ true |
+| `- [ ] scenario 9 - cyclic dependency 🆔 scenario9a ⛔️ scenario9c` | ✅ true | ✅ true |
+| `- [ ] scenario 9 - cyclic dependency 🆔 scenario9b ⛔️ scenario9a` | ✅ true | ✅ true |
+| `- [ ] scenario 9 - cyclic dependency 🆔 scenario9c ⛔️ scenario9b` | ✅ true | ✅ true |
+| `- [ ] scenario 10 - multiple dependencies TODO         - 🆔 scenario10a` | ✅ true | ❌ false |
+| `- [/] scenario 10 - multiple dependencies IN_PROGRESS  - 🆔 scenario10b` | ✅ true | ❌ false |
+| `- [x] scenario 10 - multiple dependencies DONE         - 🆔 scenario10c` | ❌ false | ❌ false |
+| `- [-] scenario 10 - multiple dependencies CANCELLED    - 🆔 scenario10d` | ❌ false | ❌ false |
+| `- [Q] scenario 10 - multiple dependencies NON_TASK     - 🆔 scenario10e` | ❌ false | ❌ false |
+| `- [ ] scenario 10 - multiple dependencies              - ⛔️ scenario10a,scenario10b,scenario10c,scenario10d,scenario10e` | ❌ false | ✅ true |
+| `- [ ] scenario 11 - indirect dependency - indirect blocking of scenario11c ignored - 🆔 scenario11a` | ❌ false | ❌ false |
+| `- [x] scenario 11 - indirect dependency - DONE                                     - 🆔 scenario11b ⛔️ scenario11a` | ❌ false | ❌ false |
+| `- [ ] scenario 11 - indirect dependency - indirect blocking of scenario11a ignored - 🆔 scenario11c ⛔️ scenario11b` | ❌ false | ❌ false |
+
+
+<!-- placeholder to force blank line after included text -->
diff --git a/tests/DocumentationSamples/DependencyDocs/DependencySamples.test.ts b/tests/DocumentationSamples/DependencyDocs/DependencySamples.test.ts
new file mode 100644
index 00000000..a0140ffd
--- /dev/null
+++ b/tests/DocumentationSamples/DependencyDocs/DependencySamples.test.ts
@@ -0,0 +1,59 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import moment from 'moment';
+
+import { StatusConfiguration, StatusType } from '../../../src/Statuses/StatusConfiguration';
+import { StatusRegistry } from '../../../src/Statuses/StatusRegistry';
+import { SearchInfo } from '../../../src/Query/SearchInfo';
+import { parseFilter } from '../../../src/Query/FilterParser';
+import { SampleTasks } from '../../TestingTools/SampleTasks';
+import { booleanToEmoji } from '../../TestingTools/FilterTestHelpers';
+import { MarkdownTable } from '../../../src/lib/MarkdownTable';
+import { verifyMarkdownForDocs } from '../../TestingTools/VerifyMarkdown';
+import { addBackticks } from '../../Scripting/ScriptingTestHelpers';
+
+window.moment = moment;
+
+function makeFilters() {
+    const instructions = [
+        //
+        'is blocking',
+        'is blocked',
+    ];
+    return instructions.map((instruction) => {
+        return parseFilter(instruction)!.filter!;
+    });
+}
+
+describe('blocking and blocked filters', () => {
+    beforeEach(() => {
+        const nonTaskStatus = new StatusConfiguration('Q', 'Question', 'A', true, StatusType.NON_TASK);
+        StatusRegistry.getInstance().add(nonTaskStatus);
+    });
+
+    afterEach(() => {
+        StatusRegistry.getInstance().resetToDefaultStatuses();
+    });
+
+    it('blocking and blocked', () => {
+        const tasks = SampleTasks.withWideSelectionOfDependencyScenarios();
+        const filters = makeFilters();
+        const searchInfo = SearchInfo.fromAllTasks(tasks);
+
+        const columnNames = ['Task'];
+        filters.forEach((filter) => columnNames.push(addBackticks(filter.instruction)));
+        const table = new MarkdownTable(columnNames);
+
+        tasks.forEach((task) => {
+            const newRow = [addBackticks(task.toFileLineString())];
+            filters.forEach((filter) => {
+                const matches = filter!.filterFunction(task, searchInfo);
+                newRow.push(booleanToEmoji(matches));
+            });
+            table.addRow(newRow);
+        });
+        verifyMarkdownForDocs(table.markdown);
+    });
+});
diff --git a/tests/Query/Filter/BlockingField.test.ts b/tests/Query/Filter/BlockingField.test.ts
index e1449f00..3ed1a1ae 100644
--- a/tests/Query/Filter/BlockingField.test.ts
+++ b/tests/Query/Filter/BlockingField.test.ts
@@ -2,15 +2,18 @@ import { TaskBuilder } from '../../TestingTools/TaskBuilder';
 import { BlockingField } from '../../../src/Query/Filter/BlockingField';
 import { BooleanField } from '../../../src/Query/Filter/BooleanField';
 import { Status } from '../../../src/Statuses/Status';
+import { fromLine } from '../../TestingTools/TestHelpers';
 
 describe('blocking', () => {
     const notBlocking = new TaskBuilder().build();
     const child = new TaskBuilder().id('12345').build();
     const childWithoutParent = new TaskBuilder().id('23456').build();
-    const parent = new TaskBuilder().blockedBy(['12345']).build();
-    const allTasks = [notBlocking, child, childWithoutParent, parent];
+    const childThatIsDone = new TaskBuilder().id('34567').status(Status.makeDone()).build();
+    const parent = new TaskBuilder().blockedBy(['12345', '34567']).build();
+    const allTasks = [notBlocking, child, childWithoutParent, childThatIsDone, parent];
 
     const isBlocking = new BlockingField().createFilterOrErrorMessage('is blocking');
+    const isNotBlocking = new BlockingField().createFilterOrErrorMessage('is not blocking');
 
     it('is blocking', () => {
         expect(isBlocking).toBeValid();
@@ -20,6 +23,18 @@ describe('blocking', () => {
         expect(isBlocking).not.toMatchTaskInTaskList(childWithoutParent, allTasks);
     });
 
+    it('is not blocking', () => {
+        expect(isNotBlocking).toBeValid();
+        expect(isNotBlocking).toMatchTaskInTaskList(notBlocking, allTasks);
+        expect(isNotBlocking).not.toMatchTaskInTaskList(child, allTasks);
+        expect(isNotBlocking).toMatchTaskInTaskList(parent, allTasks);
+        expect(isNotBlocking).toMatchTaskInTaskList(childWithoutParent, allTasks);
+    });
+
+    it('should not treat a done task as blocking', () => {
+        expect(isBlocking).not.toMatchTaskInTaskList(childThatIsDone, allTasks);
+    });
+
     it('is blocking - with circular dependencies, all tasks are matched', () => {
         const id1 = '1';
         const id2 = '2';
@@ -48,8 +63,9 @@ describe('blocking', () => {
     });
 });
 
-describe('is not blocked', () => {
+describe('blocked', () => {
     const isNotBlocked = new BlockingField().createFilterOrErrorMessage('is not blocked');
+    const isBlocked = new BlockingField().createFilterOrErrorMessage('is blocked');
     const blockingId = 'abc';
     const blocking = new TaskBuilder().id(blockingId).build();
 
@@ -58,8 +74,12 @@ describe('is not blocked', () => {
         const allTasks = [blocking, blocked];
 
         expect(isNotBlocked).toBeValid();
+
         expect(isNotBlocked).not.toMatchTaskInTaskList(blocked, allTasks);
         expect(isNotBlocked).toMatchTaskInTaskList(blocking, allTasks);
+
+        expect(isBlocked).toMatchTaskInTaskList(blocked, allTasks);
+        expect(isBlocked).not.toMatchTaskInTaskList(blocking, allTasks);
     });
 
     it('should treat completed deps as non-blocking', () => {
@@ -70,13 +90,17 @@ describe('is not blocked', () => {
         const blockedByComplete = new TaskBuilder().blockedBy([blockingCompletedId]).build();
         const blockedByAll = new TaskBuilder().blockedBy([blockingId, blockingCompletedId]).build();
 
-        const allTasks = [blocking, blockedByIncomplete, blockedByComplete, blockedByAll];
+        const allTasks = [blocking, blockedByIncomplete, blockedByComplete, blockedByAll, blockingCompleted];
 
         expect(isNotBlocked).toMatchTaskInTaskList(blockingCompleted, allTasks);
         expect(isNotBlocked).toMatchTaskInTaskList(blockedByComplete, allTasks);
         expect(isNotBlocked).not.toMatchTaskInTaskList(blockedByIncomplete, allTasks);
         expect(isNotBlocked).not.toMatchTaskInTaskList(blockedByAll, allTasks);
+    });
 
-        blocking.status.isCompleted();
+    it('should not treat completed tasks as blocked', () => {
+        const doneTaskDependingOnBlocking = fromLine({ line: `- [x] Done ⛔️ ${blockingId}` });
+        const allTasks = [blocking, doneTaskDependingOnBlocking];
+        expect(isNotBlocked).toMatchTaskInTaskList(doneTaskDependingOnBlocking, allTasks);
     });
 });
diff --git a/tests/Query/Query.test.ts b/tests/Query/Query.test.ts
index 9325be96..fbdfedf1 100644
--- a/tests/Query/Query.test.ts
+++ b/tests/Query/Query.test.ts
@@ -99,8 +99,10 @@ describe('Query parsing', () => {
         'id does not include abc123',
         'id includes abc123',
         'id includes AND', // Verify Query doesn't confuse this with a boolean query
+        'is blocked',
         'is blocking',
         'is not blocked',
+        'is not blocking',
         'is not recurring',
         'is recurring',
         'no blocked by',