Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2983 (2024-07-23 03:27:57)

refactor: create `nextOccurrence` and `nextOccurrenceDate` methods in `Occurrence`
# Types of changes

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)

## Description

- Move `Occurrence`-related behaviour to `Occurrence` class

## Motivation and Context

- Separate concerns

## How has this been tested?

- unit 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/Recurrence.ts b/src/Task/Recurrence.ts
index e0e6da79..5581a494 100644
--- a/src/Task/Recurrence.ts
+++ b/src/Task/Recurrence.ts
@@ -68,6 +68,45 @@ export class Occurrence {
 
         return true;
     }
+
+    /**
+     * Gets next occurrence (start/scheduled/due date) keeping the relative distance
+     * with the reference date
+     *
+     * @param nextReferenceDate
+     * @param currentOccurrence start/scheduled/due date
+     * @private
+     */
+    public nextOccurrenceDate(currentOccurrence: Moment | null, nextReferenceDate: Date) {
+        if (currentOccurrence === null) {
+            return null;
+        }
+        const originalDifference = window.moment.duration(currentOccurrence.diff(this.referenceDate));
+
+        // Cloning so that original won't be manipulated:
+        const nextOccurrence = window.moment(nextReferenceDate);
+        // Rounding days to handle cross daylight-savings-time recurrences.
+        nextOccurrence.add(Math.round(originalDifference.asDays()), 'days');
+        return nextOccurrence;
+    }
+
+    public nextOccurrence(next: Date): Occurrence {
+        // Only if a reference date is given. A reference date will exist if at
+        // least one of the other dates is set.
+        if (this.referenceDate === null) {
+            return new Occurrence({
+                startDate: null,
+                scheduledDate: null,
+                dueDate: null,
+            });
+        }
+
+        return new Occurrence({
+            startDate: this.nextOccurrenceDate(this.startDate, next),
+            scheduledDate: this.nextOccurrenceDate(this.scheduledDate, next),
+            dueDate: this.nextOccurrenceDate(this.dueDate, next),
+        });
+    }
 }
 
 export class Recurrence {
@@ -139,54 +178,14 @@ export class Recurrence {
      *
      * @param today - Optional date representing the completion date. Defaults to today.
      */
-    public next(today = window.moment()): {
-        startDate: Moment | null;
-        scheduledDate: Moment | null;
-        dueDate: Moment | null;
-    } | null {
+    public next(today = window.moment()): Occurrence | null {
         const next = this.nextReferenceDate(today);
 
         if (next === null) {
             return null;
         }
 
-        // Only if a reference date is given. A reference date will exist if at
-        // least one of the other dates is set.
-        if (this.occurrence.referenceDate === null) {
-            return {
-                startDate: null,
-                scheduledDate: null,
-                dueDate: null,
-            };
-        }
-
-        return {
-            startDate: this.nextOccurrence(next, this.occurrence.startDate),
-            scheduledDate: this.nextOccurrence(next, this.occurrence.scheduledDate),
-            dueDate: this.nextOccurrence(next, this.occurrence.dueDate),
-        };
-    }
-
-    /**
-     * Gets next occurrence (start/scheduled/due date) keeping the relative distance
-     * with the reference date
-     *
-     * @param nextReferenceDate
-     * @param currentOccurrence start/scheduled/due date
-     * @private
-     */
-    private nextOccurrence(nextReferenceDate: Date, currentOccurrence: Moment | null): Moment | null {
-        if (currentOccurrence === null) {
-            return null;
-        }
-
-        const originalDifference = window.moment.duration(currentOccurrence.diff(this.occurrence.referenceDate));
-
-        // Cloning so that original won't be manipulated:
-        const nextOccurrence = window.moment(nextReferenceDate);
-        // Rounding days to handle cross daylight-savings-time recurrences.
-        nextOccurrence.add(Math.round(originalDifference.asDays()), 'days');
-        return nextOccurrence;
+        return this.occurrence.nextOccurrence(next);
     }
 
     public identicalTo(other: Recurrence) {


Test patch:
diff --git a/tests/Task/Recurrence.test.ts b/tests/Task/Recurrence.test.ts
index 9a371408..d1096b01 100644
--- a/tests/Task/Recurrence.test.ts
+++ b/tests/Task/Recurrence.test.ts
@@ -23,11 +23,8 @@ describe('Recurrence', () => {
         const next = recurrence!.next();
 
         // Assert
-        expect(next).toStrictEqual({
-            startDate: null,
-            scheduledDate: null,
-            dueDate: null,
-        });
+        const nullOccurrence = new Occurrence({ startDate: null, scheduledDate: null, dueDate: null });
+        expect(next).toStrictEqual(nullOccurrence);
     });
 
     it('creates a recurrence the next month, even on the 31st', () => {