Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1114 (2022-09-05 07:44:35)

test: Add initial tests for 'Toggle task done' command
<!--- Provide a general summary of your changes in the Title above -->

# Description

Adds some automated tests for some of the behaviours of the  'Toggle task done' command.

This captures some of the improvements in behaviour made in #1103.

## Motivation and Context

- to avoid needing to manually test the  'Toggle task done' command so often
- to confirm the behaviour of many of the fixes in #1103.

## How has this been tested?

By running the tests I added.

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

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

<!--- 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! -->

- [ ] 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://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#updating-documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md#maintaining-the-tests).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md) 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 follow this project's [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)
- [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)


claremacrae: @AnnaKornfeldSimpson FYI... Absolutely no expectation that you should read or review this...

Patch:
diff --git a/src/Commands/ToggleDone.ts b/src/Commands/ToggleDone.ts
index bc14ef8b..acbd4907 100644
--- a/src/Commands/ToggleDone.ts
+++ b/src/Commands/ToggleDone.ts
@@ -51,7 +51,7 @@ export const toggleDone = (checking: boolean, editor: Editor, view: View) => {
     });
 };
 
-const toggleLine = (line: string, path: string) => {
+export const toggleLine = (line: string, path: string) => {
     let toggledLine = line;
 
     const task = Task.fromLine({
@@ -113,7 +113,7 @@ const toggleTask = (task: Task): string => {
 
 So cursor should be reset if 0, which includes being moved to new end if got shorter. Then might need to move right 2 or 3.
 */
-const calculateCursorOffset = (
+export const calculateCursorOffset = (
     origCursorCh: number,
     line: string,
     toggledLine: string,


Test patch:
diff --git a/tests/Commands/ToggleDone.test.ts b/tests/Commands/ToggleDone.test.ts
new file mode 100644
index 00000000..feb67a34
--- /dev/null
+++ b/tests/Commands/ToggleDone.test.ts
@@ -0,0 +1,131 @@
+/**
+ * @jest-environment jsdom
+ */
+
+import moment from 'moment';
+import {
+    calculateCursorOffset,
+    toggleLine,
+} from '../../src/Commands/ToggleDone';
+
+window.moment = moment;
+
+/**
+ * Test that toggling the task on the given input line generates the correct
+ * text content, and also gives the correct expected new cursor position.
+ *
+ * Cursor positions are indicated by a | (pipe) character for easy visualisation
+ * of the cursor position.
+ *
+ * @param inputWithCursorMark
+ * @param expectedWithCursorMark
+ */
+function testToggleLine(
+    inputWithCursorMark: string,
+    expectedWithCursorMark: string,
+) {
+    const cursorMarker = '|';
+    const cursorMarkerRegex = /\|/g;
+
+    const input = inputWithCursorMark.replace(cursorMarkerRegex, '');
+    const expected = expectedWithCursorMark.replace(cursorMarkerRegex, '');
+
+    // Check that the cursor marker appears exactly once in each input string:
+    expect(input.length).toEqual(inputWithCursorMark.length - 1);
+    expect(expected.length).toEqual(expectedWithCursorMark.length - 1);
+
+    testToggleLineForOutOfRangeCursorPositions(
+        input,
+        inputWithCursorMark.indexOf(cursorMarker),
+        expected,
+        expectedWithCursorMark.indexOf(cursorMarker),
+    );
+}
+
+/**
+ * Test that toggling the task on the given input line generates the correct
+ * text content, and also gives the correct expected new cursor position.
+ *
+ * Cursor positions are given by indices, where 0 is the start of the line.
+ *
+ * Call this version to represent a cursor position outside of the allowed
+ * range of character positions in the input or expected strings.
+ *
+ * @param input
+ * @param initialCursorOffset
+ * @param expected
+ * @param expectedCursorOffset
+ */
+function testToggleLineForOutOfRangeCursorPositions(
+    input: string,
+    initialCursorOffset: number,
+    expected: string,
+    expectedCursorOffset: number,
+) {
+    const result = toggleLine(input, 'x.md');
+    expect(result).toStrictEqual(expected);
+    const actualCursorOffset = calculateCursorOffset(
+        initialCursorOffset,
+        input,
+        result,
+    );
+    expect(actualCursorOffset).toEqual(expectedCursorOffset);
+}
+
+describe('ToggleDone', () => {
+    const todaySpy = jest
+        .spyOn(Date, 'now')
+        .mockReturnValue(moment('2022-09-04').valueOf());
+
+    // The | (pipe) indicates the calculated position where the cursor should be displayed.
+    // Note that prior to the #1103 fix, this position was sometimes ignored.
+
+    it('should add hyphen and space to empty line', () => {
+        testToggleLine('|', '- |');
+    });
+
+    it('should add checkbox to hyphen and space', () => {
+        testToggleLine('|- ', '- [ |] ');
+        testToggleLine('- |', '- [ ] |');
+    });
+
+    it('should complete a task', () => {
+        testToggleLine('|- [ ] ', '|- [x]  ✅ 2022-09-04');
+        testToggleLine('- [ ] |', '- [x] | ✅ 2022-09-04');
+
+        // Issue #449 - cursor jumped 13 characters to the right on completion
+        testToggleLine(
+            '- [ ] I have a |proper description',
+            '- [x] I have a |proper description ✅ 2022-09-04',
+        );
+    });
+
+    it('should un-complete a completed task', () => {
+        testToggleLine('|- [x]  ✅ 2022-09-04', '|- [ ] ');
+        testToggleLine('- [x]  ✅ 2022-09-04|', '- [ ] |');
+
+        // Issue #449 - cursor jumped 13 characters to the left on un-completion
+        testToggleLine(
+            '- [x] I have a proper description| ✅ 2022-09-04',
+            '- [ ] I have a proper description|',
+        );
+    });
+
+    it('should complete a recurring task', () => {
+        testToggleLine(
+            '- [ ] I am a recurring task| 🔁 every day 📅 2022-09-04',
+            `- [ ] I am a recurring task 🔁 every day 📅 2022-09-05
+- [x] I am a recurring task| 🔁 every day 📅 2022-09-04 ✅ 2022-09-04`,
+        );
+
+        // With a trailing space at the end of the initial line, which is deleted
+        // when the task lines are regenerated, the cursor moves one character to the left:
+        testToggleLine(
+            '- [ ] I am a recurring task| 🔁 every day 📅 2022-09-04 ',
+            `- [ ] I am a recurring task 🔁 every day 📅 2022-09-05
+- [x] I am a recurring tas|k 🔁 every day 📅 2022-09-04 ✅ 2022-09-04`,
+        );
+    });
+
+    todaySpy.mockClear();
+});