Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2819 (2024-05-13 12:19:51)

refactor: add ListItem class
<!--- Provide a general summary of your changes in the Title above -->

# Description

Add ListItem class
Also Task class extends ListItem

## Motivation and Context

Add parent-child relationships for task
And manage ListItems

## How has this been tested?

New unit tests

## Types of changes

Changes visible to users:

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

- [x] My code follows the code style of this project and passes `yarn run lint`.
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

- [x] My contribution follows 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/Task/ListItem.ts b/src/Task/ListItem.ts
new file mode 100644
index 00000000..48e38dd3
--- /dev/null
+++ b/src/Task/ListItem.ts
@@ -0,0 +1,9 @@
+export class ListItem {
+    public readonly parent: ListItem | null = null;
+    public readonly children: ListItem[];
+
+    constructor(parent: ListItem | null, children: ListItem[]) {
+        this.parent = parent;
+        this.children = children;
+    }
+}
diff --git a/src/Task/Task.ts b/src/Task/Task.ts
index f4b270e3..fa29f7e0 100644
--- a/src/Task/Task.ts
+++ b/src/Task/Task.ts
@@ -11,6 +11,7 @@ import { PriorityTools } from '../lib/PriorityTools';
 import { logging } from '../lib/logging';
 import { logEndOfTaskEdit, logStartOfTaskEdit } from '../lib/LogTasksHelper';
 import { DateFallback } from './DateFallback';
+import { ListItem } from './ListItem';
 import { Urgency } from './Urgency';
 import type { Recurrence } from './Recurrence';
 import type { TaskLocation } from './TaskLocation';
@@ -37,7 +38,7 @@ interface TaskComponents {
  * @export
  * @class Task
  */
-export class Task {
+export class Task extends ListItem {
     // NEW_TASK_FIELD_EDIT_REQUIRED
     public readonly status: Status;
     public readonly description: string;
@@ -119,6 +120,7 @@ export class Task {
         originalMarkdown: string;
         scheduledDateIsInferred: boolean;
     }) {
+        super(null, []);
         // NEW_TASK_FIELD_EDIT_REQUIRED
         this.status = status;
         this.description = description;


Test patch:
diff --git a/tests/Task/ListItem.test.ts b/tests/Task/ListItem.test.ts
new file mode 100644
index 00000000..26e6ffe3
--- /dev/null
+++ b/tests/Task/ListItem.test.ts
@@ -0,0 +1,25 @@
+import { ListItem } from '../../src/Task/ListItem';
+
+describe('list item tests', () => {
+    it('should create list item with empty children and absent parent', () => {
+        const listItem = new ListItem(null, []);
+        expect(listItem).toBeDefined();
+        expect(listItem.children).toEqual([]);
+        expect(listItem.parent).toEqual(null);
+    });
+
+    it('should create list item with a child', () => {
+        const childItem1 = new ListItem(null, []);
+        const childItem2 = new ListItem(null, []);
+        const listItem = new ListItem(null, [childItem1, childItem2]);
+        expect(listItem).toBeDefined();
+        expect(listItem.children).toEqual([childItem1, childItem2]);
+    });
+
+    it('should create list item with a parent', () => {
+        const parentItem = new ListItem(null, []);
+        const listItem = new ListItem(parentItem, []);
+        expect(listItem).toBeDefined();
+        expect(listItem.parent).toEqual(parentItem);
+    });
+});
diff --git a/tests/Task/Task.test.ts b/tests/Task/Task.test.ts
index de129142..98c218aa 100644
--- a/tests/Task/Task.test.ts
+++ b/tests/Task/Task.test.ts
@@ -47,6 +47,8 @@ describe('parsing', () => {
         expect(task!.doneDate).toEqualMoment(moment('2021-06-20'));
         expect(task!.originalMarkdown).toStrictEqual(line);
         expect(task!.lineNumber).toEqual(0);
+        expect(task.parent).toBeNull();
+        expect(task.children).toEqual([]);
     });
 
     it('parses a task from a line starting with asterisk', () => {
diff --git a/tests/TestingTools/TaskBuilder.test.ts b/tests/TestingTools/TaskBuilder.test.ts
index 61f94148..bdc8d16c 100644
--- a/tests/TestingTools/TaskBuilder.test.ts
+++ b/tests/TestingTools/TaskBuilder.test.ts
@@ -54,7 +54,7 @@ describe('TaskBuilder', () => {
     it('createFullyPopulatedTask() should populate every field', () => {
         const task: Task = TaskBuilder.createFullyPopulatedTask();
 
-        expect(getNullOrUnsetFields(task)).toEqual([]);
+        expect(getNullOrUnsetFields(task)).toEqual(['parent', 'children']);
         expect(getNullOrUnsetFields(task.taskLocation)).toEqual([]);
 
         expect(task.originalMarkdown).toEqual(