Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2501 (2023-12-12 11:30:33)

fix: prevent postpone from overwriting invalid dates.
# Description

Any editing of a task that contains an invalid date ends up replacing the date's text with the literal string `invalid date`. This loses information.

So now we do not show postpone button for tasks that contain any invalid date values.

## Motivation and Context

Avoid rewriting invalid dates such as `2023-13-35` with the literal text `invalid date`.

## How has this been tested?

Unit test added + exploratory tests.

## Types of changes

Changes visible to users:

- [x] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)

Internal changes:

- [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 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/Scripting/Postponer.ts b/src/Scripting/Postponer.ts
index d45b1781..d14fcdb8 100644
--- a/src/Scripting/Postponer.ts
+++ b/src/Scripting/Postponer.ts
@@ -4,10 +4,20 @@ import { Task } from '../Task';
 import { TasksDate } from './TasksDate';
 
 export function shouldShowPostponeButton(task: Task) {
+    // don't postpone if any invalid dates
+    for (const dateField of Task.allDateFields()) {
+        const taskElement = task[dateField] as Moment;
+        if (taskElement && !taskElement.isValid()) {
+            return false;
+        }
+    }
+
+    // require a valid happens date to postpone
     const hasAValidHappensDate = task.happensDates.some((date) => {
         return !!date?.isValid();
     });
 
+    // only postpone not done tasks
     return !task.isDone && hasAValidHappensDate;
 }
 
diff --git a/src/Task.ts b/src/Task.ts
index 0528b8e2..8a13c387 100644
--- a/src/Task.ts
+++ b/src/Task.ts
@@ -765,7 +765,7 @@ export class Task {
         }
 
         // Compare Date fields
-        args = ['createdDate', 'startDate', 'scheduledDate', 'dueDate', 'doneDate'];
+        args = Task.allDateFields();
         for (const el of args) {
             const date1 = this[el] as Moment | null;
             const date2 = other[el] as Moment | null;
@@ -787,6 +787,16 @@ export class Task {
         return true;
     }
 
+    public static allDateFields(): (keyof Task)[] {
+        return [
+            'createdDate' as keyof Task,
+            'startDate' as keyof Task,
+            'scheduledDate' as keyof Task,
+            'dueDate' as keyof Task,
+            'doneDate' as keyof Task,
+        ];
+    }
+
     /**
      * Returns an array of hashtags found in string
      *


Test patch:
diff --git a/tests/Scripting/Postponer.test.ts b/tests/Scripting/Postponer.test.ts
index eef8b4f9..accaaa55 100644
--- a/tests/Scripting/Postponer.test.ts
+++ b/tests/Scripting/Postponer.test.ts
@@ -22,6 +22,8 @@ const yesterday = '2023-12-02';
 const today = '2023-12-03';
 const tomorrow = '2023-12-04';
 
+const invalidDate = '2023-12-36';
+
 beforeEach(() => {
     jest.useFakeTimers();
     jest.setSystemTime(new Date(today));
@@ -131,7 +133,7 @@ describe('postpone - whether to show button', () => {
     });
 
     it('should not show button for a task with an invalid start date', () => {
-        const task = new TaskBuilder().startDate('2023-13-01').build();
+        const task = new TaskBuilder().startDate(invalidDate).build();
 
         expect(shouldShowPostponeButton(task)).toEqual(false);
     });
@@ -143,7 +145,7 @@ describe('postpone - whether to show button', () => {
     });
 
     it('should not show button for a task with an invalid scheduled date', () => {
-        const task = new TaskBuilder().scheduledDate('2023-12-36').build();
+        const task = new TaskBuilder().scheduledDate(invalidDate).build();
 
         expect(shouldShowPostponeButton(task)).toEqual(false);
     });
@@ -155,7 +157,13 @@ describe('postpone - whether to show button', () => {
     });
 
     it('should not show button for a task with an invalid due date', () => {
-        const task = new TaskBuilder().dueDate('20233-12-03').build();
+        const task = new TaskBuilder().dueDate(invalidDate).build();
+
+        expect(shouldShowPostponeButton(task)).toEqual(false);
+    });
+
+    it('should not show button for a task with an invalid created date', () => {
+        const task = new TaskBuilder().createdDate(invalidDate).scheduledDate(today).build();
 
         expect(shouldShowPostponeButton(task)).toEqual(false);
     });
@@ -237,7 +245,7 @@ describe('postpone - postponement success message', () => {
     });
 
     it('should generate a message for an invalid date', () => {
-        const message = postponementSuccessMessage(moment('2023-13-30'), 'dueDate');
+        const message = postponementSuccessMessage(moment(invalidDate), 'dueDate');
         expect(message).toEqual("Task's dueDate postponed until Invalid date");
     });
 });