Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2870 (2024-06-03 07:01:38)

refactor: move `parseAndValidateRecurrence()` to `EditableTask`
# 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 `parseAndValidateRecurrence()` free function to `EditableTask` class method
- improve this method tests (create `EditableTask` instance at every test, see last commit)

This is the last PR in `EditableTask`-related refactorings, I think this is better than 'good enough` so we can live with this for a while =)

## Motivation and Context

- group behaviour in the class

## How has this been tested?

- unit tests

## 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

- [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/EditTaskHelpers.ts b/src/ui/EditTaskHelpers.ts
index 71b47d03..ffdd77fe 100644
--- a/src/ui/EditTaskHelpers.ts
+++ b/src/ui/EditTaskHelpers.ts
@@ -1,6 +1,4 @@
 import { capitalizeFirstLetter } from '../lib/StringHelpers';
-import { Recurrence } from '../Task/Recurrence';
-import type { EditableTask } from './EditableTask';
 
 /**
  * Returns contents for a `<label>` HTML element.
@@ -38,28 +36,3 @@ export function labelContentWithAccessKey(labelText: string, accessKey: string |
     labelContent = capitalizeFirstLetter(labelContent);
     return labelContent;
 }
-
-export function parseAndValidateRecurrence(editableTask: EditableTask) {
-    // NEW_TASK_FIELD_EDIT_REQUIRED
-    if (!editableTask.recurrenceRule) {
-        return { parsedRecurrence: '<i>not recurring</>', isRecurrenceValid: true };
-    }
-
-    const recurrenceFromText = Recurrence.fromText({
-        recurrenceRuleText: editableTask.recurrenceRule,
-        // Only for representation in the modal, no dates required.
-        startDate: null,
-        scheduledDate: null,
-        dueDate: null,
-    })?.toText();
-
-    if (!recurrenceFromText) {
-        return { parsedRecurrence: '<i>invalid recurrence rule</i>', isRecurrenceValid: false };
-    }
-
-    if (editableTask.startDate || editableTask.scheduledDate || editableTask.dueDate) {
-        return { parsedRecurrence: recurrenceFromText, isRecurrenceValid: true };
-    }
-
-    return { parsedRecurrence: '<i>due, scheduled or start date required</i>', isRecurrenceValid: false };
-}
diff --git a/src/ui/EditableTask.ts b/src/ui/EditableTask.ts
index 8a96f95d..217bb502 100644
--- a/src/ui/EditableTask.ts
+++ b/src/ui/EditableTask.ts
@@ -240,6 +240,31 @@ export class EditableTask {
         const today = doneDate ? doneDate : window.moment();
         return updatedTask.handleNewStatusWithRecurrenceInUsersOrder(this.status, today);
     }
+
+    public parseAndValidateRecurrence() {
+        // NEW_TASK_FIELD_EDIT_REQUIRED
+        if (!this.recurrenceRule) {
+            return { parsedRecurrence: '<i>not recurring</>', isRecurrenceValid: true };
+        }
+
+        const recurrenceFromText = Recurrence.fromText({
+            recurrenceRuleText: this.recurrenceRule,
+            // Only for representation in the modal, no dates required.
+            startDate: null,
+            scheduledDate: null,
+            dueDate: null,
+        })?.toText();
+
+        if (!recurrenceFromText) {
+            return { parsedRecurrence: '<i>invalid recurrence rule</i>', isRecurrenceValid: false };
+        }
+
+        if (this.startDate || this.scheduledDate || this.dueDate) {
+            return { parsedRecurrence: recurrenceFromText, isRecurrenceValid: true };
+        }
+
+        return { parsedRecurrence: '<i>due, scheduled or start date required</i>', isRecurrenceValid: false };
+    }
 }
 
 async function serialiseTaskId(task: Task, allTasks: Task[]) {
diff --git a/src/ui/RecurrenceEditor.svelte b/src/ui/RecurrenceEditor.svelte
index e79376fd..e3265552 100644
--- a/src/ui/RecurrenceEditor.svelte
+++ b/src/ui/RecurrenceEditor.svelte
@@ -1,7 +1,7 @@
 <script lang="ts">
     import { TASK_FORMATS } from '../Config/Settings';
     import type { EditableTask } from './EditableTask';
-    import { labelContentWithAccessKey, parseAndValidateRecurrence } from './EditTaskHelpers';
+    import { labelContentWithAccessKey } from './EditTaskHelpers';
 
     export let editableTask: EditableTask;
     export let isRecurrenceValid: boolean;
@@ -9,7 +9,7 @@
 
     let parsedRecurrence: string;
 
-    $: ({ parsedRecurrence, isRecurrenceValid } = parseAndValidateRecurrence(editableTask));
+    $: ({ parsedRecurrence, isRecurrenceValid } = editableTask.parseAndValidateRecurrence());
 
     const { recurrenceSymbol } = TASK_FORMATS.tasksPluginEmoji.taskSerializer.symbols;
 </script>


Test patch:
diff --git a/tests/ui/EditTaskHelpers.test.ts b/tests/ui/EditTaskHelpers.test.ts
index fa43e1a5..51a714be 100644
--- a/tests/ui/EditTaskHelpers.test.ts
+++ b/tests/ui/EditTaskHelpers.test.ts
@@ -1,13 +1,4 @@
-/**
- * @jest-environment jsdom
- */
-
-import moment from 'moment/moment';
-import { EditableTask } from '../../src/ui/EditableTask';
-import { labelContentWithAccessKey, parseAndValidateRecurrence } from '../../src/ui/EditTaskHelpers';
-import { TaskBuilder } from '../TestingTools/TaskBuilder';
-
-window.moment = moment;
+import { labelContentWithAccessKey } from '../../src/ui/EditTaskHelpers';
 
 describe('labelContentWithAccessKey() tests', () => {
     it.each([
@@ -47,47 +38,3 @@ describe('labelContentWithAccessKey() tests', () => {
         expect(labelContentWithAccessKey(labelText, accessKey)).toEqual(labelContent);
     });
 });
-
-describe('parseAndValidateRecurrence() tests', () => {
-    const emptyTask = new TaskBuilder().description('').build();
-    const editableTask = EditableTask.fromTask(emptyTask, [emptyTask]);
-
-    const noRecurrenceRule = (editableTask: EditableTask) => {
-        editableTask.recurrenceRule = '';
-        return editableTask;
-    };
-    const invalidRecurrenceRule = (editableTask: EditableTask) => {
-        editableTask.recurrenceRule = 'thisIsWrong';
-        return editableTask;
-    };
-    const withRecurrenceRuleButNoHappensDate = (editableTask: EditableTask) => {
-        editableTask.recurrenceRule = 'every day';
-        return editableTask;
-    };
-    const withRecurrenceRuleAndHappensDate = (editableTask: EditableTask) => {
-        editableTask.recurrenceRule = 'every 1 months when done'; // confirm that recurrence text is standardised
-        editableTask.startDate = '2024-05-20';
-        return editableTask;
-    };
-
-    it.each([
-        // editable task, expected parsed recurrence, expected recurrence validity
-        [noRecurrenceRule, '<i>not recurring</>', true],
-        [invalidRecurrenceRule, '<i>invalid recurrence rule</i>', false],
-        [withRecurrenceRuleButNoHappensDate, '<i>due, scheduled or start date required</i>', false],
-        [withRecurrenceRuleAndHappensDate, 'every month when done', true],
-    ])(
-        "editable task with '%s' fields should have '%s' parsed recurrence and its validity is %s",
-        (
-            taskEditor: (editableTask: EditableTask) => EditableTask,
-            expectedParsedRecurrence: string,
-            expectedRecurrenceValidity: boolean,
-        ) => {
-            const editedTask = taskEditor(editableTask);
-
-            const { parsedRecurrence, isRecurrenceValid } = parseAndValidateRecurrence(editedTask);
-            expect(parsedRecurrence).toEqual(expectedParsedRecurrence);
-            expect(isRecurrenceValid).toEqual(expectedRecurrenceValidity);
-        },
-    );
-});
diff --git a/tests/ui/EditableTask.test.ts b/tests/ui/EditableTask.test.ts
index 884dfaa9..dd67dfda 100644
--- a/tests/ui/EditableTask.test.ts
+++ b/tests/ui/EditableTask.test.ts
@@ -239,3 +239,47 @@ describe('EditableTask tests', () => {
         expect(tasksClosestDay[0].dueDate).toEqualMoment(tuesdayAfter);
     });
 });
+
+describe('parseAndValidateRecurrence() tests', () => {
+    const emptyTask = new TaskBuilder().description('').build();
+
+    const noRecurrenceRule = (editableTask: EditableTask) => {
+        editableTask.recurrenceRule = '';
+        return editableTask;
+    };
+    const invalidRecurrenceRule = (editableTask: EditableTask) => {
+        editableTask.recurrenceRule = 'thisIsWrong';
+        return editableTask;
+    };
+    const withRecurrenceRuleButNoHappensDate = (editableTask: EditableTask) => {
+        editableTask.recurrenceRule = 'every day';
+        return editableTask;
+    };
+    const withRecurrenceRuleAndHappensDate = (editableTask: EditableTask) => {
+        editableTask.recurrenceRule = 'every 1 months when done'; // confirm that recurrence text is standardised
+        editableTask.startDate = '2024-05-20';
+        return editableTask;
+    };
+
+    it.each([
+        // editable task, expected parsed recurrence, expected recurrence validity
+        [noRecurrenceRule, '<i>not recurring</>', true],
+        [invalidRecurrenceRule, '<i>invalid recurrence rule</i>', false],
+        [withRecurrenceRuleButNoHappensDate, '<i>due, scheduled or start date required</i>', false],
+        [withRecurrenceRuleAndHappensDate, 'every month when done', true],
+    ])(
+        "editable task with '%s' fields should have '%s' parsed recurrence and its validity is %s",
+        (
+            taskEditor: (editableTask: EditableTask) => EditableTask,
+            expectedParsedRecurrence: string,
+            expectedRecurrenceValidity: boolean,
+        ) => {
+            const editableTask = EditableTask.fromTask(emptyTask, [emptyTask]);
+            const editedTask = taskEditor(editableTask);
+
+            const { parsedRecurrence, isRecurrenceValid } = editedTask.parseAndValidateRecurrence();
+            expect(parsedRecurrence).toEqual(expectedParsedRecurrence);
+            expect(isRecurrenceValid).toEqual(expectedRecurrenceValidity);
+        },
+    );
+});