Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2064 (2023-06-24 22:08:36)

refactor: Introduce QueryResult class
<!--- Provide a general summary of your changes in the Title above -->

# Description

A pure refactoring. Initially `QueryResult` is a thin wrapper around `TaskGroups` initially.

More behaviour (and more docs) will come soon.

## Motivation and Context

This is a step towards adding error-checking during execution of Tasks queries, to display an error message if an exception is thrown during the search.

This is needed for the implementation for the forthcoming `filter by function` facility.

## How has this been tested?

- Running tests
- Building and running the plugin and confirming that the rendering behaviour is unchanged.

## 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)

## 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`.
- [ ] 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/src/IQuery.ts b/src/IQuery.ts
index 913f30ac..31a9e41f 100644
--- a/src/IQuery.ts
+++ b/src/IQuery.ts
@@ -1,7 +1,7 @@
 import type { LayoutOptions } from './TaskLayout';
 import type { Task } from './Task';
-import type { TaskGroups } from './Query/TaskGroups';
 import type { Grouper } from './Query/Grouper';
+import type { QueryResult } from './Query/QueryResult';
 
 /**
  * Standard interface for the query engine used by Tasks, multiple
@@ -59,7 +59,7 @@ export interface IQuery {
      * @return {*}  {TaskGroups}
      * @memberof Query
      */
-    applyQueryToTasks: (tasks: Task[]) => TaskGroups;
+    applyQueryToTasks: (tasks: Task[]) => QueryResult;
 
     /**
      * Return a text representation of the query.
diff --git a/src/Query/Query.ts b/src/Query/Query.ts
index ca644c8c..97bebc4b 100644
--- a/src/Query/Query.ts
+++ b/src/Query/Query.ts
@@ -8,6 +8,7 @@ import { TaskGroups } from './TaskGroups';
 import * as FilterParser from './FilterParser';
 import type { Grouper } from './Grouper';
 import type { Filter } from './Filter/Filter';
+import { QueryResult } from './QueryResult';
 
 export class Query implements IQuery {
     public source: string;
@@ -170,7 +171,7 @@ export class Query implements IQuery {
         return this._error;
     }
 
-    public applyQueryToTasks(tasks: Task[]): TaskGroups {
+    public applyQueryToTasks(tasks: Task[]): QueryResult {
         this.filters.forEach((filter) => {
             tasks = tasks.filter(filter.filterFunction);
         });
@@ -185,7 +186,7 @@ export class Query implements IQuery {
             taskGroups.applyTaskLimit(this._taskGroupLimit);
         }
 
-        return taskGroups;
+        return new QueryResult(taskGroups);
     }
 
     private parseHideOptions({ line }: { line: string }): void {
diff --git a/src/Query/QueryResult.ts b/src/Query/QueryResult.ts
new file mode 100644
index 00000000..75e53008
--- /dev/null
+++ b/src/Query/QueryResult.ts
@@ -0,0 +1,18 @@
+import type { TaskGroups } from './TaskGroups';
+import type { TaskGroup } from './TaskGroup';
+
+export class QueryResult {
+    public readonly taskGroups: TaskGroups;
+
+    constructor(groups: TaskGroups) {
+        this.taskGroups = groups;
+    }
+
+    public get totalTasksCount(): number {
+        return this.taskGroups.totalTasksCount();
+    }
+
+    public get groups(): TaskGroup[] {
+        return this.taskGroups.groups;
+    }
+}
diff --git a/src/QueryRenderer.ts b/src/QueryRenderer.ts
index 5d0f3f98..186931f5 100644
--- a/src/QueryRenderer.ts
+++ b/src/QueryRenderer.ts
@@ -168,10 +168,10 @@ class QueryRenderChild extends MarkdownRenderChild {
             this.createExplanation(content);
         }
 
-        const tasksSortedLimitedGrouped = this.query.applyQueryToTasks(tasks);
-        await this.addAllTaskGroups(tasksSortedLimitedGrouped, content);
+        const queryResult = this.query.applyQueryToTasks(tasks);
+        await this.addAllTaskGroups(queryResult.taskGroups, content);
 
-        const totalTasksCount = tasksSortedLimitedGrouped.totalTasksCount();
+        const totalTasksCount = queryResult.totalTasksCount;
         console.debug(`${totalTasksCount} of ${tasks.length} tasks displayed in a block in "${this.filePath}"`);
         this.addTaskCount(content, totalTasksCount);
     }


Test patch:
diff --git a/tests/Config/DebugSettings.test.ts b/tests/Config/DebugSettings.test.ts
index 7691ca2c..fefabbc7 100644
--- a/tests/Config/DebugSettings.test.ts
+++ b/tests/Config/DebugSettings.test.ts
@@ -28,11 +28,11 @@ describe('DebugSettings', () => {
         updateSettings({ debugSettings: new DebugSettings(true) });
 
         // Act
-        const groups = query.applyQueryToTasks(tasks);
+        const queryResult = query.applyQueryToTasks(tasks);
 
         // Assert
-        expect(groups.groups.length).toEqual(1);
-        const soleTaskGroup = groups.groups[0];
+        expect(queryResult.groups.length).toEqual(1);
+        const soleTaskGroup = queryResult.groups[0];
         // Check that the tasks are found in the original order, not the order in the sort instruction
         expect('\n' + soleTaskGroup.tasksAsStringOfLines()).toStrictEqual(tasksAsMarkdown);
 
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index 3331789a..6461f7cd 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -966,11 +966,11 @@ At most 8 tasks per group (if any "group by" options are supplied).
             const tasks = createTasksFromMarkdown(tasksAsMarkdown, 'some_markdown_file', 'Some Heading');
 
             // Act
-            const groups = query.applyQueryToTasks(tasks);
+            const queryResult = query.applyQueryToTasks(tasks);
 
             // Assert
-            expect(groups.groups.length).toEqual(1);
-            const soleTaskGroup = groups.groups[0];
+            expect(queryResult.groups.length).toEqual(1);
+            const soleTaskGroup = queryResult.groups[0];
             const expectedTasks = `
 - [ ] Task 3 - will be sorted to 1st place, so should pass limit
 - [ ] Task 4 - will be sorted to 2nd place, so should pass limit
@@ -1004,17 +1004,17 @@ At most 8 tasks per group (if any "group by" options are supplied).
             const tasks = createTasksFromMarkdown(tasksAsMarkdown, 'some_markdown_file', 'Some Heading');
 
             // Act
-            const groups = query.applyQueryToTasks(tasks);
+            const queryResult = query.applyQueryToTasks(tasks);
 
             // Assert
-            expect(groups.groups.length).toEqual(2);
-            expect(groups.totalTasksCount()).toEqual(5);
-            expect(groups.groups[0].tasksAsStringOfLines()).toMatchInlineSnapshot(`
+            expect(queryResult.groups.length).toEqual(2);
+            expect(queryResult.totalTasksCount).toEqual(5);
+            expect(queryResult.groups[0].tasksAsStringOfLines()).toMatchInlineSnapshot(`
                 "- [x] Task 1 - will be in the first group
                 - [x] Task 2 - will be in the first group and sorted after next one
                 "
             `);
-            expect(groups.groups[1].tasksAsStringOfLines()).toMatchInlineSnapshot(`
+            expect(queryResult.groups[1].tasksAsStringOfLines()).toMatchInlineSnapshot(`
                 "- [ ] Task 3 - will be sorted to 1st place in the second group and pass the limit
                 - [ ] Task 4 - will be sorted to 2nd place in the second group and pass the limit
                 - [ ] Task 5 - will be sorted to 3nd place in the second group and pass the limit
diff --git a/tests/Query/QueryResult.test.ts b/tests/Query/QueryResult.test.ts
new file mode 100644
index 00000000..e0190614
--- /dev/null
+++ b/tests/Query/QueryResult.test.ts
@@ -0,0 +1,20 @@
+import { TaskGroups } from '../../src/Query/TaskGroups';
+import type { Grouper } from '../../src/Query/Grouper';
+import type { Task } from '../../src/Task';
+import { QueryResult } from '../../src/Query/QueryResult';
+
+describe('QueryResult', () => {
+    it('should create a QueryResult from TaskGroups', () => {
+        // Arrange
+        const groupers: Grouper[] = [];
+        const tasks: Task[] = [];
+        const groups = new TaskGroups(groupers, tasks);
+
+        // Act
+        const queryResult = new QueryResult(groups);
+
+        // Assert
+        expect(queryResult.totalTasksCount).toEqual(0);
+        expect(queryResult.groups).toEqual(groups.groups);
+    });
+});