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

Issue:
Console error when 'Toggle task done' command deletes the task line
### ⚠️ Check issue is valid.

- [X] My request is about the [Tasks plugin for Obsidian](https://publish.obsidian.md/tasks/Introduction).

### ⚠️ Check that this issue hasn't been reported before.

- [X] I searched previous [Tasks plugin 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.

### Steps to reproduce

**Set up Tasks:**

1. Launch Obsidian sandbox vault
2. Install and enable Tasks plugin - 7.14.0 is the current version.

**Set up the test file:**

3. Create a new note, e.g. `Test`
4. Switch to Source Mode
5. Paste in this content:

~~~
Sample

- [ ] #task Delete me 🏁 delete

Sample
~~~

**Trigger the error:**

6. Show the developer console (`Cmd + Opt + I` on Mac)
7. Put the cursor at the start of the task line
8. Run the command `Tasks: Toggle task done`

### Expected Behavior

Task line is deleted.
And there is no console error.

### Current behaviour

Task line is deleted.
There is a console error:

~~~
plugin:obsidian-tasks-plugin:217 Uncaught TypeError: Cannot read properties of undefined (reading 'length')
    at WF (plugin:obsidian-tasks-plugin:217:11)
    at Object.Rk [as editorCheckCallback] (plugin:obsidian-tasks-plugin:215:350)
    at e.mobileOnly.Wl.isMobile.e.checkCallback (app.js:1:1954754)
    at X_ (app.js:1:1954070)
    at t.onChooseItem (app.js:1:2568787)
    at t.onChooseSuggestion (app.js:1:1780074)
    at t.selectSuggestion (app.js:1:1779544)
    at e.useSelectedItem (app.js:1:1410251)
    at Object.func (app.js:1:1407650)
    at e.handleKey (app.js:1:771021)
WF	@	plugin:obsidian-tasks-plugin:217
Rk	@	plugin:obsidian-tasks-plugin:215
e.mobileOnly.Wl.isMobile.e.checkCallback	@	app.js:1
X_	@	app.js:1
t.onChooseItem	@	app.js:1
t.onChooseSuggestion	@	app.js:1
t.selectSuggestion	@	app.js:1
e.useSelectedItem	@	app.js:1
(anonymous)	@	app.js:1
e.handleKey	@	app.js:1
e.onKeyEvent	@	app.js:1
~~~



### Which Operating Systems are you using?

- [ ] Android
- [ ] iPhone/iPad
- [ ] Linux
- [X] macOS
- [ ] Windows

### Obsidian Version

1.8.0

### Tasks Plugin Version

7.14.0

### Checks

- [X] I have tried it with all other plugins disabled and the error still occurs

### Possible solution

_No response_

Pull request description:
fix: Remove console error when line is deleted by 'Toggle task done' command
# Types of changes


Changes visible to users:

- [x] **Bug fix** (prefix: `fix` - non-breaking change which fixes an issue)
  - Issue/discussion: Fixes #3256

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 -->

## Motivation and Context

Fix a traceback error discovered earlier today:

- https://github.com/obsidian-tasks-group/obsidian-tasks/issues/3256

## How has this been tested?

- Adding failing tests - then fixing them
- Running existing tests
- Confirming fix by exploratory testing


## 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 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/Commands/ToggleDone.ts b/src/Commands/ToggleDone.ts
index f7f5fa10..6ccc25a2 100644
--- a/src/Commands/ToggleDone.ts
+++ b/src/Commands/ToggleDone.ts
@@ -75,7 +75,8 @@ export const toggleLine = (line: string, path: string): EditorInsertion => {
     });
     if (task !== null) {
         const lines = task.toggleWithRecurrenceInUsersOrder().map((t) => t.toFileLineString());
-        return { text: lines.join('\n'), moveTo: { line: lines.length - 1 } };
+        const newLineNumber = lines.length > 0 ? lines.length - 1 : 0;
+        return { text: lines.join('\n'), moveTo: { line: newLineNumber } };
     } else {
         // If the task is null this means that we have one of:
         // 1. a regular checklist item


Test patch:
diff --git a/tests/Commands/ToggleDone.test.ts b/tests/Commands/ToggleDone.test.ts
index 3158439a..1a3f19f0 100644
--- a/tests/Commands/ToggleDone.test.ts
+++ b/tests/Commands/ToggleDone.test.ts
@@ -182,6 +182,40 @@ describe('ToggleDone', () => {
         );
     });
 
+    describe('on completion', () => {
+        it('should delete a self-deleting task - cursor at start of line', () => {
+            // Issue #3256 - traceback occurred.
+            testToggleLine(
+                // Force linebreak
+                '|- [ ] #task Delete me 🏁 delete',
+                '|',
+            );
+        });
+
+        it('should delete a self-deleting task - cursor at end of line', () => {
+            // Issue #3256 - traceback occurred.
+            testToggleLine(
+                // Force linebreak
+                '- [ ] #task Delete me 🏁 delete|',
+                '|',
+            );
+        });
+
+        it('should discard completed recurring task - cursor at start of line', () => {
+            testToggleLine(
+                '|- [ ] #task Delete my completed task 🔁 every day 🏁 delete ⏳ 2024-12-31',
+                '|- [ ] #task Delete my completed task 🔁 every day 🏁 delete ⏳ 2025-01-01',
+            );
+        });
+
+        it('should discard completed recurring task - cursor at end of line', () => {
+            testToggleLine(
+                '- [ ] #task Delete my completed task 🔁 every day 🏁 delete ⏳ 2024-12-31|',
+                '- [ ] #task Delete my completed task 🔁 every day 🏁 delete ⏳ 2025-01-01|',
+            );
+        });
+    });
+
     describe('should honour next status character', () => {
         afterEach(() => {
             GlobalFilter.getInstance().reset();