Repository: obsidian-tasks-group/obsidian-tasks (ts)

Issue:
Edit Modal ignores edits to cancelled date
### Please check that this issue hasn't been reported before.

- [X] I searched previous [Bug Reports](https://github.com/obsidian-tasks-group/obsidian-tasks/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+bug%22+sort%3Acomments-desc) didn't find any similar reports.

### Expected Behavior

In Edit Modal setting status to cancelled and setting canceled date to any non today's date and clicking Apply button should save the task with the input cancelled date.

Example:

today is 2024-09-01
I set status to cancelled, the cancelled date is 2024-09-01, I set the cancelled date to any other date -> the task is saved with 2024-09-01 as cancelled date

### Current behaviour

In Edit Modal setting status to cancelled and canceled date to any non today's date and clicking Apply button saves the task with today's cancelled date.

Example:

today is 2024-09-01
I set status to cancelled, the cancelled date is 2024-09-01, I set the cancelled date to 2022-02-02 -> the task is saved with 2022-02-02 as cancelled date

### Steps to reproduce

1. Open Edit Task modal on an existing task `- [ ] #task todo` (Global filter is set to `#task`)
2. Set status to Cancelled (THIS IS THE IMPORTANT STEP, IF OMITTED, EVERYTHING WORKS FINE)
3. Set cancelled date to any date than today's date
4. Click Apply

### Which Operating Systems are you using?

- [ ] Android
- [ ] iPhone/iPad
- [ ] Linux
- [X] macOS
- [ ] Windows

### Obsidian Version

1.7.1

### Tasks Plugin Version

7.10.0

### Checks

- [ ] I have tried it with all other plugins disabled and the error still occurs

The cancelled date is enabled in the settings.

### Possible solution

_No response_

Pull request description:
fix: Edit Task modal now retains edits to Cancelled date
# 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] **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)

## Description

<!--- Remember to provide a general summary of your changes in the Title above -->

<!--- Describe your changes in detail -->

When changing status type to CANCELLED, use the Cancelled Date as `today` instead of the `Done Date`.

## Motivation and Context

Fixes #3089

- #3089

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How has this been tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

- running existing tests
    - this actually fixed two of the existing tests, not just the one that linked to the issue being fixed here
- manual exploratory testing

## Screenshots (if appropriate)

## 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 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 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/ui/EditableTask.ts b/src/ui/EditableTask.ts
index d6fd4ec8..2c54dc33 100644
--- a/src/ui/EditableTask.ts
+++ b/src/ui/EditableTask.ts
@@ -8,6 +8,7 @@ import { Priority } from '../Task/Priority';
 import { Recurrence } from '../Task/Recurrence';
 import { Task } from '../Task/Task';
 import { addDependencyToParent, ensureTaskHasId, generateUniqueId, removeDependency } from '../Task/TaskDependency';
+import { StatusType } from '../Statuses/StatusConfiguration';
 
 type EditableTaskPriority = 'none' | 'lowest' | 'low' | 'medium' | 'high' | 'highest';
 
@@ -242,12 +243,40 @@ export class EditableTask {
 
         // Then apply the new status to the updated task, in case a new recurrence
         // needs to be created.
-        // If there is a 'done' date, use that for today's date for recurrence calculations.
-        // Otherwise, use the current date.
-        const today = doneDate ? doneDate : window.moment();
+        const today = this.inferTodaysDate(this.status.type, doneDate, cancelledDate);
         return updatedTask.handleNewStatusWithRecurrenceInUsersOrder(this.status, today);
     }
 
+    /**
+     * If the user has manually edited the Done date or Cancelled date in the modal,
+     * we need to tell Tasks to use a different `today` value in the status-editing code.
+     * Here we calculate that inferred date.
+     */
+    private inferTodaysDate(
+        newStatusType: StatusType,
+        doneDate: moment.Moment | null,
+        cancelledDate: moment.Moment | null,
+    ) {
+        if (newStatusType === StatusType.DONE && doneDate !== null) {
+            // The status type of the edited task is DONE, so we need to preserve the
+            // Done Date value in the modal as today's date,
+            // for use in later code.
+            // This is needed for scenarios including:
+            //  - The task already had a done date before being edited
+            //  - The user changed the status to Done, and then edited the machine-generted done date.
+            return doneDate;
+        }
+
+        if (newStatusType === StatusType.CANCELLED && cancelledDate !== null) {
+            // The status type of the edited task is CANCELLED, so we need to preserve the
+            // Cancelled Date value in the modal as today's date.
+            return cancelledDate;
+        }
+
+        // Otherwise, use the current date.
+        return window.moment();
+    }
+
     public parseAndValidateRecurrence() {
         // NEW_TASK_FIELD_EDIT_REQUIRED
         if (!this.recurrenceRule) {


Test patch:
diff --git a/tests/ui/EditTask.test.ts b/tests/ui/EditTask.test.ts
index 18f790e0..bf9dca77 100644
--- a/tests/ui/EditTask.test.ts
+++ b/tests/ui/EditTask.test.ts
@@ -418,7 +418,7 @@ describe('Task editing', () => {
 
             submit.click();
             expect(await waitForClose).toMatchInlineSnapshot(
-                '"- [-] expecting cancelled date to be kept ❌ 2024-02-29"',
+                '"- [-] expecting cancelled date to be kept ❌ 2024-09-20"',
             );
         });
 
@@ -476,9 +476,8 @@ describe('Task editing', () => {
                 'cancelled',
                 '2024-09-21',
                 '-',
-                // TODO the difference between the date in the modal and the saved one is a bug:
                 // https://github.com/obsidian-tasks-group/obsidian-tasks/issues/3089
-                '- [-] input cancelled date, change status to cancelled and expect the date to be kept ❌ 2024-02-29',
+                '- [-] input cancelled date, change status to cancelled and expect the date to be kept ❌ 2024-09-21',
             ],
         ])(
             'for "%s" task, change %s date to %s and status to %s',