Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1531 (2023-01-14 13:53:46)

refactor: Move StatusConfiguration to its own file
<!--- Provide a general summary of your changes in the Title above -->

# Description

Move `StatusConfiguration` to its own source and test file.

## Motivation and Context

It's been growing, I'm going to be adding some new methods shortly - so it's time to separate it out.

## How has this been tested?

Running existing tests

## 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)

## 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://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)



Patch:
diff --git a/src/Config/CustomStatusModal.ts b/src/Config/CustomStatusModal.ts
index e4a3e2cd..359e2955 100644
--- a/src/Config/CustomStatusModal.ts
+++ b/src/Config/CustomStatusModal.ts
@@ -1,5 +1,6 @@
 import { Modal, Notice, Setting, TextComponent } from 'obsidian';
-import { Status, StatusConfiguration } from '../Status';
+import { Status } from '../Status';
+import { StatusConfiguration } from '../StatusConfiguration';
 import type TasksPlugin from '../main';
 
 export class CustomStatusModal extends Modal {
diff --git a/src/Config/SettingsTab.ts b/src/Config/SettingsTab.ts
index 072104a8..c7733a76 100644
--- a/src/Config/SettingsTab.ts
+++ b/src/Config/SettingsTab.ts
@@ -1,5 +1,6 @@
 import { Notice, PluginSettingTab, Setting, debounce } from 'obsidian';
-import { Status, StatusConfiguration } from 'Status';
+import { Status } from 'Status';
+import { StatusConfiguration } from '../StatusConfiguration';
 import type TasksPlugin from '../main';
 import { StatusRegistry } from '../StatusRegistry';
 import type { HeadingState } from './Settings';
diff --git a/src/Config/StatusSettings.ts b/src/Config/StatusSettings.ts
index 5abbc994..91b46ede 100644
--- a/src/Config/StatusSettings.ts
+++ b/src/Config/StatusSettings.ts
@@ -1,4 +1,4 @@
-import { StatusConfiguration } from '../Status';
+import { StatusConfiguration } from '../StatusConfiguration';
 import type { StatusRegistry } from '../StatusRegistry';
 
 /**
diff --git a/src/Config/StatusSettingsHelpers.ts b/src/Config/StatusSettingsHelpers.ts
index 1514b035..2a202076 100644
--- a/src/Config/StatusSettingsHelpers.ts
+++ b/src/Config/StatusSettingsHelpers.ts
@@ -3,7 +3,8 @@
  * It intentionally does not import any Obsidian types, so that tests can
  * be written for its contents.
  */
-import { Status, StatusConfiguration } from '../Status';
+import { Status } from '../Status';
+import type { StatusConfiguration } from '../StatusConfiguration';
 
 /**
  * Return a one-line summary of the status, for presentation to users.
diff --git a/src/Status.ts b/src/Status.ts
index 18f6e62b..0d2b0dde 100644
--- a/src/Status.ts
+++ b/src/Status.ts
@@ -1,102 +1,4 @@
-/**
- * This is the object stored by the Obsidian configuration and used to create the status
- * objects for the session
- *
- * @export
- * @class StatusConfiguration
- */
-export class StatusConfiguration {
-    /**
-     * The indicator used between the two square brackets in the markdown task.
-     *
-     * @type {string}
-     * @memberof Status
-     */
-    public readonly indicator: string;
-
-    /**
-     * Returns the name of the status for display purposes.
-     *
-     * @type {string}
-     * @memberof Status
-     */
-    public readonly name: string;
-
-    /**
-     * Returns the next status for a task when toggled.
-     *
-     * @type {string}
-     * @memberof Status
-     */
-    public readonly nextStatusIndicator: string;
-
-    /**
-     * If true then it is registered as a command that the user can map to.
-     *
-     * @type {boolean}
-     * @memberof Status
-     */
-    public readonly availableAsCommand: boolean;
-
-    /**
-     * Creates an instance of Status. The registry will be added later in the case
-     * of the default statuses.
-     *
-     * @param {string} indicator
-     * @param {string} name
-     * @param {Status} nextStatusIndicator
-     * @param {boolean} availableAsCommand
-     * @memberof Status
-     */
-    constructor(indicator: string, name: string, nextStatusIndicator: string, availableAsCommand: boolean) {
-        this.indicator = indicator;
-        this.name = name;
-        this.nextStatusIndicator = nextStatusIndicator;
-        this.availableAsCommand = availableAsCommand;
-    }
-
-    /**
-     * Determine whether the date in this object is valid, and return error message(s) for display if not.
-     */
-    public validate(): string[] {
-        const errors: string[] = [];
-
-        // Messages are added in the order fields are shown when editing statuses.
-        errors.push(...this.validateIndicator());
-        errors.push(...this.validateName());
-        errors.push(...this.validateNextIndicator());
-
-        return errors;
-    }
-
-    public validateIndicator(): string[] {
-        return StatusConfiguration.validateOneIndicator(this.indicator, 'Task Status Symbol');
-    }
-
-    public validateNextIndicator(): string[] {
-        return StatusConfiguration.validateOneIndicator(this.nextStatusIndicator, 'Task Next Status Symbol');
-    }
-
-    public validateName() {
-        const errors: string[] = [];
-        if (this.name.length === 0) {
-            errors.push('Task Status Name cannot be empty.');
-        }
-        return errors;
-    }
-
-    private static validateOneIndicator(indicator: string, indicatorName: string): string[] {
-        const errors: string[] = [];
-        if (indicator.length === 0) {
-            errors.push(`${indicatorName} cannot be empty.`);
-        }
-
-        if (indicator.length > 1) {
-            errors.push(`${indicatorName} ("${indicator}") must be a single character.`);
-        }
-        return errors;
-    }
-}
+import { StatusConfiguration } from './StatusConfiguration';
 
 /**
  * Tracks the possible states that a task can be in.
diff --git a/src/StatusConfiguration.ts b/src/StatusConfiguration.ts
new file mode 100644
index 00000000..e5039f92
--- /dev/null
+++ b/src/StatusConfiguration.ts
@@ -0,0 +1,99 @@
+/**
+ * This is the object stored by the Obsidian configuration and used to create the status
+ * objects for the session
+ *
+ * @export
+ * @class StatusConfiguration
+ */
+export class StatusConfiguration {
+    /**
+     * The indicator used between the two square brackets in the markdown task.
+     *
+     * @type {string}
+     * @memberof Status
+     */
+    public readonly indicator: string;
+
+    /**
+     * Returns the name of the status for display purposes.
+     *
+     * @type {string}
+     * @memberof Status
+     */
+    public readonly name: string;
+
+    /**
+     * Returns the next status for a task when toggled.
+     *
+     * @type {string}
+     * @memberof Status
+     */
+    public readonly nextStatusIndicator: string;
+
+    /**
+     * If true then it is registered as a command that the user can map to.
+     *
+     * @type {boolean}
+     * @memberof Status
+     */
+    public readonly availableAsCommand: boolean;
+
+    /**
+     * Creates an instance of Status. The registry will be added later in the case
+     * of the default statuses.
+     *
+     * @param {string} indicator
+     * @param {string} name
+     * @param {Status} nextStatusIndicator
+     * @param {boolean} availableAsCommand
+     * @memberof Status
+     */
+    constructor(indicator: string, name: string, nextStatusIndicator: string, availableAsCommand: boolean) {
+        this.indicator = indicator;
+        this.name = name;
+        this.nextStatusIndicator = nextStatusIndicator;
+        this.availableAsCommand = availableAsCommand;
+    }
+
+    /**
+     * Determine whether the date in this object is valid, and return error message(s) for display if not.
+     */
+    public validate(): string[] {
+        const errors: string[] = [];
+
+        // Messages are added in the order fields are shown when editing statuses.
+        errors.push(...this.validateIndicator());
+        errors.push(...this.validateName());
+        errors.push(...this.validateNextIndicator());
+
+        return errors;
+    }
+
+    public validateIndicator(): string[] {
+        return StatusConfiguration.validateOneIndicator(this.indicator, 'Task Status Symbol');
+    }
+
+    public validateNextIndicator(): string[] {
+        return StatusConfiguration.validateOneIndicator(this.nextStatusIndicator, 'Task Next Status Symbol');
+    }
+
+    public validateName() {
+        const errors: string[] = [];
+        if (this.name.length === 0) {
+            errors.push('Task Status Name cannot be empty.');
+        }
+        return errors;
+    }
+
+    private static validateOneIndicator(indicator: string, indicatorName: string): string[] {
+        const errors: string[] = [];
+        if (indicator.length === 0) {
+            errors.push(`${indicatorName} cannot be empty.`);
+        }
+
+        if (indicator.length > 1) {
+            errors.push(`${indicatorName} ("${indicator}") must be a single character.`);
+        }
+        return errors;
+    }
+}
diff --git a/src/StatusRegistry.ts b/src/StatusRegistry.ts
index 3dff2f3e..d34e8707 100644
--- a/src/StatusRegistry.ts
+++ b/src/StatusRegistry.ts
@@ -1,4 +1,5 @@
-import { Status, StatusConfiguration } from './Status';
+import { Status } from './Status';
+import type { StatusConfiguration } from './StatusConfiguration';
 
 /**
  * Tracks all the registered statuses a task can have.


Test patch:
diff --git a/tests/Commands/ToggleDone.test.ts b/tests/Commands/ToggleDone.test.ts
index 592b8195..48d59107 100644
--- a/tests/Commands/ToggleDone.test.ts
+++ b/tests/Commands/ToggleDone.test.ts
@@ -6,7 +6,8 @@ import moment from 'moment';
 import { calculateCursorOffset, toggleLine } from '../../src/Commands/ToggleDone';
 import { resetSettings, updateSettings } from '../../src/Config/Settings';
 import { StatusRegistry } from '../../src/StatusRegistry';
-import { Status, StatusConfiguration } from '../../src/Status';
+import { Status } from '../../src/Status';
+import { StatusConfiguration } from '../../src/StatusConfiguration';
 
 window.moment = moment;
 
diff --git a/tests/Config/StatusSettings.test.ts b/tests/Config/StatusSettings.test.ts
index 2ba3122a..99ffdab8 100644
--- a/tests/Config/StatusSettings.test.ts
+++ b/tests/Config/StatusSettings.test.ts
@@ -1,6 +1,7 @@
 import { verifyAsJson } from 'approvals/lib/Providers/Jest/JestApprovals';
 import { StatusSettings } from '../../src/Config/StatusSettings';
-import { Status, StatusConfiguration } from '../../src/Status';
+import { Status } from '../../src/Status';
+import { StatusConfiguration } from '../../src/StatusConfiguration';
 import { StatusRegistry } from '../../src/StatusRegistry';
 
 describe('StatusSettings', () => {
diff --git a/tests/DocsSamplesForStatuses.test.ts b/tests/DocsSamplesForStatuses.test.ts
index 0737a3f8..b829ada4 100644
--- a/tests/DocsSamplesForStatuses.test.ts
+++ b/tests/DocsSamplesForStatuses.test.ts
@@ -2,7 +2,8 @@ import { Options } from 'approvals/lib/Core/Options';
 import { verify } from 'approvals/lib/Providers/Jest/JestApprovals';
 
 import { StatusRegistry } from '../src/StatusRegistry';
-import { Status, StatusConfiguration } from '../src/Status';
+import { Status } from '../src/Status';
+import { StatusConfiguration } from '../src/StatusConfiguration';
 import * as StatusSettingsHelpers from '../src/Config/StatusSettingsHelpers';
 
 function getPrintableIndicator(indicator: string) {
diff --git a/tests/Status.test.ts b/tests/Status.test.ts
index 74f5a92f..9e216f49 100644
--- a/tests/Status.test.ts
+++ b/tests/Status.test.ts
@@ -2,90 +2,12 @@
  * @jest-environment jsdom
  */
 import moment from 'moment';
-import { Status, StatusConfiguration } from '../src/Status';
+import { Status } from '../src/Status';
+import { StatusConfiguration } from '../src/StatusConfiguration';
 
 jest.mock('obsidian');
 window.moment = moment;
 
-describe('StatusConfiguration', () => {
-    function checkValidation(statusConfiguration: StatusConfiguration, expectedMessages: string[]) {
-        const errors = statusConfiguration.validate();
-        expect(errors).toEqual(expectedMessages);
-    }
-
-    describe('validation', () => {
-        it('should handle valid input correctly', () => {
-            const config = new StatusConfiguration('X', 'Completed', ' ', false);
-            checkValidation(config, []);
-        });
-
-        it('should handle totally invalid input correctly', () => {
-            const config = new StatusConfiguration('Xxx', '', '', false);
-            checkValidation(config, [
-                'Task Status Symbol ("Xxx") must be a single character.',
-                'Task Status Name cannot be empty.',
-                'Task Next Status Symbol cannot be empty.',
-            ]);
-        });
-
-        // Check status name
-        it('should detect empty name', () => {
-            const config = new StatusConfiguration('X', '', ' ', false);
-            checkValidation(config, ['Task Status Name cannot be empty.']);
-        });
-
-        // Check Symbol
-        it('should detect empty symbol', () => {
-            const config = new StatusConfiguration('', 'Completed', ' ', false);
-            checkValidation(config, ['Task Status Symbol cannot be empty.']);
-        });
-
-        it('should detect too-long symbol', () => {
-            const config = new StatusConfiguration('yyy', 'Completed', ' ', false);
-            checkValidation(config, ['Task Status Symbol ("yyy") must be a single character.']);
-        });
-
-        // Check Next symbol
-        it('should detect next empty symbol', () => {
-            const config = new StatusConfiguration('X', 'Completed', '', false);
-            checkValidation(config, ['Task Next Status Symbol cannot be empty.']);
-        });
-
-        it('should detect too-long next symbol', () => {
-            const config = new StatusConfiguration('X', 'Completed', 'yyy', false);
-            checkValidation(config, ['Task Next Status Symbol ("yyy") must be a single character.']);
-        });
-
-        describe('validate indicator', () => {
-            it('valid indicator', () => {
-                const config = new StatusConfiguration('X', 'Completed', 'c', false);
-                expect(config.validateIndicator()).toStrictEqual([]);
-            });
-
-            it('invalid indicator', () => {
-                const config = new StatusConfiguration('XYZ', 'Completed', 'c', false);
-                expect(config.validateIndicator()).toStrictEqual([
-                    'Task Status Symbol ("XYZ") must be a single character.',
-                ]);
-            });
-        });
-
-        describe('validate next indicator', () => {
-            it('valid indicator', () => {
-                const config = new StatusConfiguration('c', 'Completed', 'X', false);
-                expect(config.validateNextIndicator()).toStrictEqual([]);
-            });
-
-            it('invalid next indicator', () => {
-                const config = new StatusConfiguration('c', 'Completed', 'XYZ', false);
-                expect(config.validateNextIndicator()).toStrictEqual([
-                    'Task Next Status Symbol ("XYZ") must be a single character.',
-                ]);
-            });
-        });
-    });
-});
-
 describe('Status', () => {
     it('should initialize with valid properties', () => {
         // Arrange
diff --git a/tests/StatusConfiguration.test.ts b/tests/StatusConfiguration.test.ts
new file mode 100644
index 00000000..51605683
--- /dev/null
+++ b/tests/StatusConfiguration.test.ts
@@ -0,0 +1,80 @@
+import { StatusConfiguration } from '../src/StatusConfiguration';
+
+describe('StatusConfiguration', () => {
+    function checkValidation(statusConfiguration: StatusConfiguration, expectedMessages: string[]) {
+        const errors = statusConfiguration.validate();
+        expect(errors).toEqual(expectedMessages);
+    }
+
+    describe('validation', () => {
+        it('should handle valid input correctly', () => {
+            const config = new StatusConfiguration('X', 'Completed', ' ', false);
+            checkValidation(config, []);
+        });
+
+        it('should handle totally invalid input correctly', () => {
+            const config = new StatusConfiguration('Xxx', '', '', false);
+            checkValidation(config, [
+                'Task Status Symbol ("Xxx") must be a single character.',
+                'Task Status Name cannot be empty.',
+                'Task Next Status Symbol cannot be empty.',
+            ]);
+        });
+
+        // Check status name
+        it('should detect empty name', () => {
+            const config = new StatusConfiguration('X', '', ' ', false);
+            checkValidation(config, ['Task Status Name cannot be empty.']);
+        });
+
+        // Check Symbol
+        it('should detect empty symbol', () => {
+            const config = new StatusConfiguration('', 'Completed', ' ', false);
+            checkValidation(config, ['Task Status Symbol cannot be empty.']);
+        });
+
+        it('should detect too-long symbol', () => {
+            const config = new StatusConfiguration('yyy', 'Completed', ' ', false);
+            checkValidation(config, ['Task Status Symbol ("yyy") must be a single character.']);
+        });
+
+        // Check Next symbol
+        it('should detect next empty symbol', () => {
+            const config = new StatusConfiguration('X', 'Completed', '', false);
+            checkValidation(config, ['Task Next Status Symbol cannot be empty.']);
+        });
+
+        it('should detect too-long next symbol', () => {
+            const config = new StatusConfiguration('X', 'Completed', 'yyy', false);
+            checkValidation(config, ['Task Next Status Symbol ("yyy") must be a single character.']);
+        });
+
+        describe('validate indicator', () => {
+            it('valid indicator', () => {
+                const config = new StatusConfiguration('X', 'Completed', 'c', false);
+                expect(config.validateIndicator()).toStrictEqual([]);
+            });
+
+            it('invalid indicator', () => {
+                const config = new StatusConfiguration('XYZ', 'Completed', 'c', false);
+                expect(config.validateIndicator()).toStrictEqual([
+                    'Task Status Symbol ("XYZ") must be a single character.',
+                ]);
+            });
+        });
+
+        describe('validate next indicator', () => {
+            it('valid indicator', () => {
+                const config = new StatusConfiguration('c', 'Completed', 'X', false);
+                expect(config.validateNextIndicator()).toStrictEqual([]);
+            });
+
+            it('invalid next indicator', () => {
+                const config = new StatusConfiguration('c', 'Completed', 'XYZ', false);
+                expect(config.validateNextIndicator()).toStrictEqual([
+                    'Task Next Status Symbol ("XYZ") must be a single character.',
+                ]);
+            });
+        });
+    });
+});
diff --git a/tests/StatusRegistry.test.ts b/tests/StatusRegistry.test.ts
index 83981b36..81760a98 100644
--- a/tests/StatusRegistry.test.ts
+++ b/tests/StatusRegistry.test.ts
@@ -3,7 +3,8 @@
  */
 import moment from 'moment';
 import { StatusRegistry } from '../src/StatusRegistry';
-import { Status, StatusConfiguration } from '../src/Status';
+import { Status } from '../src/Status';
+import { StatusConfiguration } from '../src/StatusConfiguration';
 import { Task } from '../src/Task';
 import * as TestHelpers from './TestHelpers';