Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2586 (2024-01-16 23:15:28)

feat: Enable use of Query Properties like query.file.path in 'sort by function'
<!--- Provide a general summary of your changes in the Title above -->

# Description

Enable `sort by function` lines to use `query.file.path` and other [Query Properties](https://publish.obsidian.md/tasks/Scripting/Query+Properties).

## Motivation and Context

Desperate to get `sort by function` released!

## How has this been tested?

- New tests
- Exploratory testing
- New query added to demo vault to show its use.

## Screenshots (if appropriate)

![image](https://github.com/obsidian-tasks-group/obsidian-tasks/assets/4840096/3df7ad32-dfd5-4410-88cf-62b3d348cfde)


## 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] **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] **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)

## 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.
- [ ] 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/resources/sample_vaults/Tasks-Demo/Functions/Custom Sorting.md b/resources/sample_vaults/Tasks-Demo/Functions/Custom Sorting.md
index 29861e41..db370618 100644
--- a/resources/sample_vaults/Tasks-Demo/Functions/Custom Sorting.md
+++ b/resources/sample_vaults/Tasks-Demo/Functions/Custom Sorting.md
@@ -2,9 +2,21 @@
 
 This file contains samples for experimenting with 'sort by function'.
 
+## Tasks
+
+- [ ] #task **I am in Custom Sorting.md**
+
 ## Examples
 
-==TODO==
+```tasks
+folder includes {{query.file.folder}}
+
+# Put tasks in the same file as the query first.
+# Important:
+#     It's a bit counter-intuitive:
+#     False sort keys sort first, so we reverse the result, to get the desired results.
+sort by function reverse task.file.path === query.file.path
+```
 
 ## Error Handling
 
diff --git a/src/Query/Filter/FunctionField.ts b/src/Query/Filter/FunctionField.ts
index 80cc7e9f..fe9326c9 100644
--- a/src/Query/Filter/FunctionField.ts
+++ b/src/Query/Filter/FunctionField.ts
@@ -73,10 +73,11 @@ export class FunctionField extends Field {
             // will add that information.
             throw new Error(taskExpression.parseError);
         }
-        const comparator = (a: Task, b: Task) => {
+        const comparator = (a: Task, b: Task, searchInfo: SearchInfo) => {
             try {
-                const valueA = this.validateTaskSortKey(taskExpression.evaluate(a));
-                const valueB = this.validateTaskSortKey(taskExpression.evaluate(b));
+                const queryContext = searchInfo.queryContext();
+                const valueA = this.validateTaskSortKey(taskExpression.evaluate(a, queryContext));
+                const valueB = this.validateTaskSortKey(taskExpression.evaluate(b, queryContext));
                 return this.compareTaskSortKeys(valueA, valueB);
             } catch (exception) {
                 if (exception instanceof Error) {
diff --git a/src/Query/Query.ts b/src/Query/Query.ts
index 2db2afdf..24d3b553 100644
--- a/src/Query/Query.ts
+++ b/src/Query/Query.ts
@@ -250,7 +250,7 @@ Problem line: "${line}"`;
             });
 
             const { debugSettings } = getSettings();
-            const tasksSorted = debugSettings.ignoreSortInstructions ? tasks : Sort.by(this.sorting, tasks);
+            const tasksSorted = debugSettings.ignoreSortInstructions ? tasks : Sort.by(this.sorting, tasks, searchInfo);
             const tasksSortedLimited = tasksSorted.slice(0, this.limit);
 
             const taskGroups = new TaskGroups(this.grouping, tasksSortedLimited, searchInfo);
diff --git a/src/Query/Sort.ts b/src/Query/Sort.ts
index b660e72b..6cb9d2de 100644
--- a/src/Query/Sort.ts
+++ b/src/Query/Sort.ts
@@ -6,9 +6,12 @@ import { DueDateField } from './Filter/DueDateField';
 import { PriorityField } from './Filter/PriorityField';
 import { PathField } from './Filter/PathField';
 import { UrgencyField } from './Filter/UrgencyField';
+import type { SearchInfo } from './SearchInfo';
+
+type PlainComparator = (a: Task, b: Task) => number;
 
 export class Sort {
-    public static by(sorters: Sorter[], tasks: Task[]) {
+    public static by(sorters: Sorter[], tasks: Task[], searchInfo: SearchInfo) {
         const defaultComparators: Comparator[] = [
             new UrgencyField().comparator(),
             new StatusField().comparator(),
@@ -23,13 +26,13 @@ export class Sort {
             userComparators.push(sorter.comparator);
         }
 
-        return tasks.sort(Sort.makeCompositeComparator([...userComparators, ...defaultComparators]));
+        return tasks.sort(Sort.makeCompositeComparator([...userComparators, ...defaultComparators], searchInfo));
     }
 
-    private static makeCompositeComparator(comparators: Comparator[]): Comparator {
+    private static makeCompositeComparator(comparators: Comparator[], searchInfo: SearchInfo): PlainComparator {
         return (a, b) => {
             for (const comparator of comparators) {
-                const result = comparator(a, b);
+                const result = comparator(a, b, searchInfo);
                 if (result !== 0) {
                     return result;
                 }
diff --git a/src/Query/Sorter.ts b/src/Query/Sorter.ts
index 06567c6e..658e3402 100644
--- a/src/Query/Sorter.ts
+++ b/src/Query/Sorter.ts
@@ -1,4 +1,5 @@
 import type { Task } from '../Task';
+import type { SearchInfo } from './SearchInfo';
 
 /**
  * A sorting function, that takes two Task objects and returns
@@ -8,8 +9,11 @@ import type { Task } from '../Task';
  * - `0` or sometimes `-0`, if a equals b by the ordering criterion.
  *
  * Typically Comparator functions are stored in a {@link Sorter} object.
+ *
+ * The {@link SearchInfo} parameter allows implementations to inspect selected information
+ * about the {@link Query} containing the search being run.
  */
-export type Comparator = (a: Task, b: Task) => number;
+export type Comparator = (a: Task, b: Task, searchInfo: SearchInfo) => number;
 
 /**
  * Sorter represents a single 'sort by' instruction.
@@ -41,6 +45,6 @@ export class Sorter {
 
     private static makeReversedComparator(comparator: Comparator): Comparator {
         // Note: This can return -0.
-        return (a, b) => (comparator(a, b) * -1) as -1 | 0 | 1;
+        return (a, b, searchInfo) => (comparator(a, b, searchInfo) * -1) as -1 | 0 | 1;
     }
 }


Test patch:
diff --git a/tests/CustomMatchers/CustomMatchersForSorting.ts b/tests/CustomMatchers/CustomMatchersForSorting.ts
index 32a2d6b3..b34d5e61 100644
--- a/tests/CustomMatchers/CustomMatchersForSorting.ts
+++ b/tests/CustomMatchers/CustomMatchersForSorting.ts
@@ -3,6 +3,7 @@ import { DateParser } from '../../src/Query/DateParser';
 import type { Sorter } from '../../src/Query/Sorter';
 import type { Task } from '../../src/Task';
 import { compareByDate } from '../../src/lib/DateTools';
+import { SearchInfo } from '../../src/Query/SearchInfo';
 
 declare global {
     namespace jest {
@@ -38,7 +39,7 @@ expect.extend({
         expect(tasks.length).toEqual(2);
         const taskA = tasks[0];
         const taskB = tasks[1];
-        const actual = sorting.comparator(taskA, taskB);
+        const actual = sorting.comparator(taskA, taskB, new SearchInfo('dummy path.md', tasks));
 
         let pass;
         let expectedDesription: string;
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index b0fda805..ab5f040a 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -1190,9 +1190,11 @@ describe('Query', () => {
             const query = new Query('sort by status reverse');
             const sorter = query.sorting[0];
 
-            expect(sorter!.comparator(todoTask, doneTask)).toEqual(1);
-            expect(sorter!.comparator(doneTask, doneTask)).toEqual(-0); // Note the minus sign. It's a consequence of
-            expect(sorter!.comparator(doneTask, todoTask)).toEqual(-1);
+            const searchInfo = SearchInfo.fromAllTasks([todoTask, doneTask]);
+
+            expect(sorter!.comparator(todoTask, doneTask, searchInfo)).toEqual(1);
+            expect(sorter!.comparator(doneTask, doneTask, searchInfo)).toEqual(-0); // Note the minus sign. It's a consequence of
+            expect(sorter!.comparator(doneTask, todoTask, searchInfo)).toEqual(-1);
         });
     });
 
diff --git a/tests/Query/Filter/DescriptionField.test.ts b/tests/Query/Filter/DescriptionField.test.ts
index 271d9aad..73836760 100644
--- a/tests/Query/Filter/DescriptionField.test.ts
+++ b/tests/Query/Filter/DescriptionField.test.ts
@@ -8,13 +8,13 @@ import { testTaskFilter } from '../../TestingTools/FilterTestHelpers';
 import { fromLine } from '../../TestHelpers';
 import type { FilterOrErrorMessage } from '../../../src/Query/Filter/FilterOrErrorMessage';
 import { BooleanField } from '../../../src/Query/Filter/BooleanField';
-import { Sort } from '../../../src/Query/Sort';
 import {
     expectTaskComparesAfter,
     expectTaskComparesBefore,
     expectTaskComparesEqual,
 } from '../../CustomMatchers/CustomMatchersForSorting';
 import { TaskBuilder } from '../../TestingTools/TaskBuilder';
+import { sortBy } from '../../TestingTools/SortingTestHelpers';
 
 window.moment = moment;
 
@@ -392,7 +392,7 @@ describe('sorting by description', () => {
         });
 
         const expectedOrder = [one, two, three, four, five];
-        expect(Sort.by([new DescriptionField().createNormalSorter()], [two, one, five, four, three])).toEqual(
+        expect(sortBy([new DescriptionField().createNormalSorter()], [two, one, five, four, three])).toEqual(
             expectedOrder,
         );
     });
diff --git a/tests/Query/Filter/FunctionField.test.ts b/tests/Query/Filter/FunctionField.test.ts
index 07615b4f..3cea0527 100644
--- a/tests/Query/Filter/FunctionField.test.ts
+++ b/tests/Query/Filter/FunctionField.test.ts
@@ -422,6 +422,19 @@ The error message was:
             expect(sorter).not.toBeNull();
             expectTaskComparesBefore(sorter!, todoTask, doneTask);
         });
+
+        it('sort by function - using query.file.path', () => {
+            // Arrange
+            const sorter = new FunctionField().createSorterFromLine(
+                'sort by function task.file.path === query.file.path',
+            );
+            const todoTask = fromLine({ line: '- [ ] todo' });
+            const doneTask = fromLine({ line: '- [x] done' });
+
+            // Assert
+            expect(sorter).not.toBeNull();
+            expectTaskComparesEqual(sorter!, todoTask, doneTask);
+        });
     });
 });
 
diff --git a/tests/Query/Filter/TagsField.test.ts b/tests/Query/Filter/TagsField.test.ts
index f10e00c3..c236a8a5 100644
--- a/tests/Query/Filter/TagsField.test.ts
+++ b/tests/Query/Filter/TagsField.test.ts
@@ -4,12 +4,12 @@ import { shouldSupportFiltering } from '../../TestingTools/FilterTestHelpers';
 import { TagsField } from '../../../src/Query/Filter/TagsField';
 import { DescriptionField } from '../../../src/Query/Filter/DescriptionField';
 import { fromLine } from '../../TestHelpers';
-import { Sort } from '../../../src/Query/Sort';
 import { TaskBuilder } from '../../TestingTools/TaskBuilder';
 import { expectTaskComparesAfter, expectTaskComparesBefore } from '../../CustomMatchers/CustomMatchersForSorting';
 import type { Grouper } from '../../../src/Query/Grouper';
 import { TaskGroups } from '../../../src/Query/TaskGroups';
 import { SearchInfo } from '../../../src/Query/SearchInfo';
+import { sortBy } from '../../TestingTools/SortingTestHelpers';
 
 describe('tag presence & absence', () => {
     it.each(['has tag', 'has tags'])('should have "%s" filtering', (filterLine: string) => {
@@ -364,13 +364,13 @@ describe('Sort by tags', () => {
 
         it('should create a default comparator, sorting by first tag', () => {
             const comparator = tagsField.comparator();
-            expect(comparator(tags_a_d, tags_a_c)).toBeLessThan(0);
+            expect(comparator(tags_a_d, tags_a_c, SearchInfo.fromAllTasks([tags_a_d, tags_a_c]))).toBeLessThan(0);
         });
 
         it('should parse a valid line with default tag number', () => {
             const sorter = tagsField.createSorterFromLine('sort by tag');
             expect(sorter?.property).toEqual('tag');
-            expect(sorter?.comparator(tag_a, tag_b)).toBeLessThan(0);
+            expect(sorter?.comparator(tag_a, tag_b, SearchInfo.fromAllTasks([tag_a, tag_b]))).toBeLessThan(0);
             expectTaskComparesBefore(sorter!, tag_a, tag_b);
         });
 
@@ -422,10 +422,7 @@ describe('Sort by tags', () => {
 
         // Act / Assert
         expect(
-            Sort.by(
-                [new TagsField().createSorterFromLine('sort by tag 1')!],
-                [t1, t3, t5, t7, t6, t4, t2, t8, t9, t10],
-            ),
+            sortBy([new TagsField().createSorterFromLine('sort by tag 1')!], [t1, t3, t5, t7, t6, t4, t2, t8, t9, t10]),
         ).toEqual(expectedOrder);
     });
 
@@ -445,7 +442,7 @@ describe('Sort by tags', () => {
 
         // Act / Assert
         expect(
-            Sort.by(
+            sortBy(
                 [new TagsField().createSorterFromLine('sort by tag reverse 1')!],
                 [t1, t3, t5, t7, t6, t4, t2, t8, t9, t10],
             ),
@@ -459,7 +456,7 @@ describe('Sort by tags', () => {
         const t4 = fromLine({ line: '- [ ] a #iii #ddd' });
         const t5 = fromLine({ line: '- [ ] a #hhh #eee' });
         const expectedOrder = [t1, t2, t3, t4, t5];
-        expect(Sort.by([new TagsField().createSorterFromLine('sort by tag 2')!], [t4, t3, t2, t1, t5])).toEqual(
+        expect(sortBy([new TagsField().createSorterFromLine('sort by tag 2')!], [t4, t3, t2, t1, t5])).toEqual(
             expectedOrder,
         );
     });
@@ -471,7 +468,7 @@ describe('Sort by tags', () => {
         const t4 = fromLine({ line: '- [ ] a #iii #ddd' });
         const t5 = fromLine({ line: '- [ ] a #hhh #eee' });
         const expectedOrder = [t5, t4, t3, t2, t1];
-        expect(Sort.by([new TagsField().createSorterFromLine('sort by tag reverse 2')!], [t4, t3, t2, t1, t5])).toEqual(
+        expect(sortBy([new TagsField().createSorterFromLine('sort by tag reverse 2')!], [t4, t3, t2, t1, t5])).toEqual(
             expectedOrder,
         );
     });
@@ -498,7 +495,7 @@ describe('Sort by tags', () => {
 
         // Act
         expect(
-            Sort.by(
+            sortBy(
                 [new TagsField().createSorterFromLine('sort by tag 1')!],
                 [t1, t12, t3, t13, t5, t7, t6, t4, t2, t8, t9, t10, t11],
             ),
@@ -530,7 +527,7 @@ describe('Sort by tags', () => {
 
         // Act
         expect(
-            Sort.by(
+            sortBy(
                 [new TagsField().createSorterFromLine('sort by tag reverse 1')!],
                 [t1, t12, t3, t13, t5, t7, t6, t4, t2, t8, t9, t10, t11],
             ),
@@ -555,7 +552,7 @@ describe('Sort by tags', () => {
         const expectedOrder = [t1, t2, t3, t4, t5, t6, t7, t8];
 
         // Act
-        const result = Sort.by(
+        const result = sortBy(
             [new TagsField().createSorterFromLine('sort by tag 2')!],
             [t4, t7, t5, t2, t3, t1, t8, t6],
         );
@@ -582,7 +579,7 @@ describe('Sort by tags', () => {
         const expectedOrder = [t8, t7, t6, t5, t4, t3, t2, t1];
 
         // Act
-        const result = Sort.by(
+        const result = sortBy(
             [new TagsField().createSorterFromLine('sort by tag reverse 2')!],
             [t4, t7, t5, t2, t3, t1, t8, t6],
         );
@@ -619,7 +616,7 @@ describe('Sort by tags', () => {
             input[8],
         ];
         expect(
-            Sort.by(
+            sortBy(
                 [
                     new TagsField().createSorterFromLine('sort by tag 2')!, // tag 2 - ascending
                     new TagsField().createSorterFromLine('sort by tag 1')!, // tag 1 - ascending
diff --git a/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_docs.approved.md b/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_docs.approved.md
index a01d7239..412f06f4 100644
--- a/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_docs.approved.md
+++ b/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_docs.approved.md
@@ -7,5 +7,12 @@ sort by function task.file.folder
 
 - Enable sorting by the folder containing the task.
 
+```javascript
+sort by function reverse task.file.path === query.file.path
+```
+
+- Sort tasks in the same file as the query before tasks in other files.
+- **Note**: `false` sort keys sort first, so we `reverse` the result, to get the desired results.
+
 
 <!-- placeholder to force blank line after included text -->
diff --git a/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_results.approved.txt b/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_results.approved.txt
index 832f74c5..b84555ed 100644
--- a/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_results.approved.txt
+++ b/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.file_properties_task.file.folder_results.approved.txt
@@ -16,3 +16,19 @@ Enable sorting by the folder containing the task.
 - [ ] xyz in 'e/d/c.md' in heading 'heading'
 ====================================================================================
 
+
+sort by function reverse task.file.path === query.file.path
+Sort tasks in the same file as the query before tasks in other files.
+**Note**: `false` sort keys sort first, so we `reverse` the result, to get the desired results.
+=>
+- [ ] xyz in 'a/b.md' in heading 'null'
+- [ ] xyz in '' in heading 'heading'
+- [ ] xyz in 'a_b_c.md' in heading 'a_b_c'
+- [ ] xyz in 'a/b/_c_.md' in heading 'null'
+- [ ] xyz in 'a/b/_c_.md' in heading 'heading _italic text_'
+- [ ] xyz in 'a/b/c.md' in heading 'null'
+- [ ] xyz in 'a/b/c.md' in heading 'c'
+- [ ] xyz in 'a/d/c.md' in heading 'heading'
+- [ ] xyz in 'e/d/c.md' in heading 'heading'
+====================================================================================
+
diff --git a/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.ts b/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.ts
index 3dedef82..99d40f06 100644
--- a/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.ts
+++ b/tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.ts
@@ -136,6 +136,11 @@ describe('file properties', () => {
             [
                 // comment to force line break
                 ['sort by function task.file.folder', 'Enable sorting by the folder containing the task'],
+                [
+                    'sort by function reverse task.file.path === query.file.path',
+                    'Sort tasks in the same file as the query before tasks in other files.',
+                    '**Note**: `false` sort keys sort first, so we `reverse` the result, to get the desired results.',
+                ],
             ],
             tasks,
         ],
diff --git a/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts b/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts
index 45ce65c0..b1272b6a 100644
--- a/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts
+++ b/tests/Scripting/ScriptingReference/VerifyFunctionFieldSamples.ts
@@ -130,7 +130,7 @@ export function verifyFunctionFieldSortSamplesOnTasks(
         const sorter = new FunctionField().createSorterFromLine(expandedInstruction);
         expect(sorter).not.toBeNull();
 
-        const tasksSorted = Sort.by([sorter!], tasks);
+        const tasksSorted = Sort.by([sorter!], tasks, new SearchInfo(path, tasks));
         return formatQueryAndResultsForApproving(instruction, comment, toLines(tasksSorted));
     });
 }
diff --git a/tests/Sort.test.ts b/tests/Sort.test.ts
index 45cfece7..16482d2f 100644
--- a/tests/Sort.test.ts
+++ b/tests/Sort.test.ts
@@ -6,14 +6,15 @@ import moment from 'moment';
 window.moment = moment;
 
 import type { Comparator } from '../src/Query/Sorter';
-import { Sort } from '../src/Query/Sort';
 import { Sorter } from '../src/Query/Sorter';
 import type { Task } from '../src/Task';
 import { StatusField } from '../src/Query/Filter/StatusField';
 import { DueDateField } from '../src/Query/Filter/DueDateField';
 import { PathField } from '../src/Query/Filter/PathField';
+import { SearchInfo } from '../src/Query/SearchInfo';
 import { fromLine } from './TestHelpers';
 import { TaskBuilder } from './TestingTools/TaskBuilder';
+import { sortBy } from './TestingTools/SortingTestHelpers';
 
 describe('Sort', () => {
     it('constructs Sorting both ways from Comparator function', () => {
@@ -29,20 +30,22 @@ describe('Sort', () => {
         const short = new TaskBuilder().description('short').build();
         const long = new TaskBuilder().description('longer description').build();
 
+        const searchInfo = SearchInfo.fromAllTasks([short, long]);
+
         // Normal way round
         {
             const sortByDescriptionLength = new Sorter('sort by description length', 'junk', comparator, false);
-            expect(sortByDescriptionLength.comparator(short, long)).toEqual(1);
-            expect(sortByDescriptionLength.comparator(short, short)).toEqual(0);
-            expect(sortByDescriptionLength.comparator(long, short)).toEqual(-1);
+            expect(sortByDescriptionLength.comparator(short, long, searchInfo)).toEqual(1);
+            expect(sortByDescriptionLength.comparator(short, short, searchInfo)).toEqual(0);
+            expect(sortByDescriptionLength.comparator(long, short, searchInfo)).toEqual(-1);
         }
 
         // Reversed
         {
             const sortByDescriptionLength = new Sorter('sort by description length reverse', 'junk', comparator, true);
-            expect(sortByDescriptionLength.comparator(short, long)).toEqual(-1);
-            expect(sortByDescriptionLength.comparator(short, short)).toEqual(-0);
-            expect(sortByDescriptionLength.comparator(long, short)).toEqual(1);
+            expect(sortByDescriptionLength.comparator(short, long, searchInfo)).toEqual(-1);
+            expect(sortByDescriptionLength.comparator(short, short, searchInfo)).toEqual(-0);
+            expect(sortByDescriptionLength.comparator(long, short, searchInfo)).toEqual(1);
         }
     });
 
@@ -54,7 +57,7 @@ describe('Sort', () => {
         const five = fromLine({ line: '- [x] b 📅 1970-01-02', path: '3' });
         const six = fromLine({ line: '- [x] d 📅 1970-01-03', path: '2' });
         const expectedOrder = [one, two, three, four, five, six];
-        expect(Sort.by([], [six, five, one, four, two, three])).toEqual(expectedOrder);
+        expect(sortBy([], [six, five, one, four, two, three])).toEqual(expectedOrder);
     });
 
     // Just a couple of tests to verify the handling of
@@ -72,7 +75,7 @@ describe('Sort', () => {
             four, // Done tasks are sorted after open tasks for status.
         ];
         expect(
-            Sort.by(
+            sortBy(
                 [
                     new DueDateField().createNormalSorter(),
                     new PathField().createNormalSorter(),
@@ -94,7 +97,7 @@ describe('Sort', () => {
         const expectedOrder = [one, two, three, four, five, six];
 
         expect(
-            Sort.by(
+            sortBy(
                 [
                     new StatusField().createReverseSorter(),
                     new DueDateField().createReverseSorter(),
diff --git a/tests/TestingTools/SortingTestHelpers.ts b/tests/TestingTools/SortingTestHelpers.ts
new file mode 100644
index 00000000..ae594004
--- /dev/null
+++ b/tests/TestingTools/SortingTestHelpers.ts
@@ -0,0 +1,8 @@
+import type { Sorter } from '../../src/Query/Sorter';
+import type { Task } from '../../src/Task';
+import { Sort } from '../../src/Query/Sort';
+import { SearchInfo } from '../../src/Query/SearchInfo';
+
+export function sortBy(sorters: Sorter[], tasks: Task[]) {
+    return Sort.by(sorters, tasks, SearchInfo.fromAllTasks(tasks));
+}