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

Issue:
unreleased bug: Some "refactoring" broke reading of status settings
### 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

I should be able to save status settings, quit the program and restart it - and the custom statuses should be retained.

### Current behaviour

A traceback

![image](https://user-images.githubusercontent.com/4840096/212500456-7874e9da-fc4c-4efc-bcd2-0ad6ba7ad529.png)


### Steps to reproduce

- Build 07dbcf96d8e62db8e78593e786e15517b9e7e7cc
- Install plugin in `resources/sample_vaults/Tasks-Demo/`
- Open the Tasks settings
- Add a few custom statuses
- Quit Obsidian
- Restart Obsidian
- Go the Tasks settings

Expected behaviour:
- The custom statuses are still present

Actual behaviour
- The widgets below the Custom Task Statuses section are missing
- This is shown in the console

![image](https://user-images.githubusercontent.com/4840096/212500533-8ecc9771-423c-4f6f-93fa-35c550dcfbb7.png)


### Which Operating Systems are you using?

- [ ] Android
- [ ] iPhone/iPad
- [ ] Linux
- [X] macOS
- [ ] Windows

### Obsidian Version

1.1.12

### Tasks Plugin Version

07dbcf96d8e62db8e78593e786e15517b9e7e7cc

### Checks

- [ ] I have tried it with all other plugins disabled and the error still occurs

### Possible solution

TIL that "objects" read from JSON lose their methods.

So now I need to move out the methods that I recently added to `StatusConfiguration`.

Pull request:
bug: reading of status settings now works again
<!--- Provide a general summary of your changes in the Title above -->

# Description

- See #1535 for details. In what I thought were safe refactorings, I had broken reading of status settings by putting some methods on `StatusConfiguration` that were missing when the custom statuses were read from data.json.
- Also added some saved settings files from different Tasks versions, to enable manual testing for backwards-compatibility of settings-reading.

## Motivation and Context

Fixes #1535

## How has this been tested?

By running in Obsidian

## Screenshots (if appropriate)

## 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] **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! -->

- [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.
- [ ] 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/resources/sample_vaults/Tasks-Demo/Settings Files/data 1.22.0.json b/resources/sample_vaults/Tasks-Demo/Settings Files/data 1.22.0.json
new file mode 100644
index 00000000..e1b865fd
--- /dev/null
+++ b/resources/sample_vaults/Tasks-Demo/Settings Files/data 1.22.0.json
@@ -0,0 +1,14 @@
+{
+  "globalFilter": "#task",
+  "removeGlobalFilter": true,
+  "setDoneDate": true,
+  "autoSuggestInEditor": true,
+  "autoSuggestMinMatch": 0,
+  "autoSuggestMaxItems": 6,
+  "provideAccessKeys": true,
+  "useFilenameAsScheduledDate": true,
+  "filenameAsDateFolders": [],
+  "features": {
+    "INTERNAL_TESTING_ENABLED_BY_DEFAULT": true
+  }
+}
\ No newline at end of file
diff --git a/resources/sample_vaults/Tasks-Demo/Settings Files/data 1.23.0-pre-release-build-1.json b/resources/sample_vaults/Tasks-Demo/Settings Files/data 1.23.0-pre-release-build-1.json
new file mode 100644
index 00000000..444cd289
--- /dev/null
+++ b/resources/sample_vaults/Tasks-Demo/Settings Files/data 1.23.0-pre-release-build-1.json
@@ -0,0 +1,35 @@
+{
+  "globalFilter": "#task",
+  "removeGlobalFilter": true,
+  "setDoneDate": true,
+  "autoSuggestInEditor": true,
+  "autoSuggestMinMatch": 0,
+  "autoSuggestMaxItems": 6,
+  "provideAccessKeys": true,
+  "useFilenameAsScheduledDate": true,
+  "filenameAsDateFolders": [],
+  "statusSettings": {
+    "customStatusTypes": [
+      {
+        "indicator": "P",
+        "name": "Pro",
+        "nextStatusIndicator": "C",
+        "availableAsCommand": false
+      },
+      {
+        "indicator": "C",
+        "name": "Con",
+        "nextStatusIndicator": "P",
+        "availableAsCommand": false
+      }
+    ]
+  },
+  "features": {
+    "INTERNAL_TESTING_ENABLED_BY_DEFAULT": true
+  },
+  "generalSettings": {},
+  "headingOpened": {
+    "Core Status Types": true,
+    "Custom Status Types": true
+  }
+}
\ No newline at end of file
diff --git a/src/Config/CustomStatusModal.ts b/src/Config/CustomStatusModal.ts
index 21dc0b46..153ce53f 100644
--- a/src/Config/CustomStatusModal.ts
+++ b/src/Config/CustomStatusModal.ts
@@ -1,6 +1,10 @@
 import { Modal, Notice, Setting, TextComponent } from 'obsidian';
 import { StatusConfiguration } from '../StatusConfiguration';
 import type TasksPlugin from '../main';
+import { StatusValidator } from '../StatusValidator';
+import { Status } from '../Status';
+
+const validator = new StatusValidator();
 
 export class CustomStatusModal extends Modal {
     statusSymbol: string;
@@ -45,12 +49,12 @@ export class CustomStatusModal extends Modal {
                 statusSymbolText = text;
                 text.setValue(this.statusSymbol).onChange((v) => {
                     this.statusSymbol = v;
-                    CustomStatusModal.setValid(text, this.statusConfiguration().validateIndicator());
+                    CustomStatusModal.setValid(text, validator.validateIndicator(this.statusConfiguration()));
                 });
             })
             .then((_setting) => {
                 // Show any error if the initial value loaded is incorrect.
-                CustomStatusModal.setValid(statusSymbolText, this.statusConfiguration().validateIndicator());
+                CustomStatusModal.setValid(statusSymbolText, validator.validateIndicator(this.statusConfiguration()));
             });
 
         let statusNameText: TextComponent;
@@ -61,11 +65,11 @@ export class CustomStatusModal extends Modal {
                 statusNameText = text;
                 text.setValue(this.statusName).onChange((v) => {
                     this.statusName = v;
-                    CustomStatusModal.setValid(text, this.statusConfiguration().validateName());
+                    CustomStatusModal.setValid(text, validator.validateName(this.statusConfiguration()));
                 });
             })
             .then((_setting) => {
-                CustomStatusModal.setValid(statusNameText, this.statusConfiguration().validateName());
+                CustomStatusModal.setValid(statusNameText, validator.validateName(this.statusConfiguration()));
             });
 
         let statusNextSymbolText: TextComponent;
@@ -76,14 +80,17 @@ export class CustomStatusModal extends Modal {
                 statusNextSymbolText = text;
                 text.setValue(this.statusNextSymbol).onChange((v) => {
                     this.statusNextSymbol = v;
-                    CustomStatusModal.setValid(text, this.statusConfiguration().validateNextIndicator());
+                    CustomStatusModal.setValid(text, validator.validateNextIndicator(this.statusConfiguration()));
                 });
             })
             .then((_setting) => {
-                CustomStatusModal.setValid(statusNextSymbolText, this.statusConfiguration().validateNextIndicator());
+                CustomStatusModal.setValid(
+                    statusNextSymbolText,
+                    validator.validateNextIndicator(this.statusConfiguration()),
+                );
             });
 
-        if (StatusConfiguration.tasksPluginCanCreateCommandsForStatuses()) {
+        if (Status.tasksPluginCanCreateCommandsForStatuses()) {
             new Setting(settingDiv)
                 .setName('Available as command')
                 .setDesc(
@@ -102,7 +109,7 @@ export class CustomStatusModal extends Modal {
             b.setTooltip('Save')
                 .setIcon('checkmark')
                 .onClick(async () => {
-                    const errors = this.statusConfiguration().validate();
+                    const errors = validator.validate(this.statusConfiguration());
                     if (errors.length > 0) {
                         const message = errors.join('\n') + '\n\n' + 'Fix errors before saving.';
                         // console.debug(message);
diff --git a/src/Config/SettingsTab.ts b/src/Config/SettingsTab.ts
index 487d7412..4feb2d21 100644
--- a/src/Config/SettingsTab.ts
+++ b/src/Config/SettingsTab.ts
@@ -2,6 +2,7 @@ import { Notice, PluginSettingTab, Setting, debounce } from 'obsidian';
 import { StatusConfiguration } from '../StatusConfiguration';
 import type TasksPlugin from '../main';
 import { StatusRegistry } from '../StatusRegistry';
+import { Status } from '../Status';
 import type { HeadingState } from './Settings';
 import { getSettings, isFeatureEnabled, updateGeneralSetting, updateSettings } from './Settings';
 import { StatusSettings } from './StatusSettings';
@@ -368,10 +369,10 @@ export class SettingsTab extends PluginSettingTab {
     insertTaskCoreStatusSettings(containerEl: HTMLElement, settings: SettingsTab) {
         // TODO Make these statuses editable
         const coreStatuses: StatusSettings = new StatusSettings();
-        StatusSettings.addCustomStatus(coreStatuses, StatusConfiguration.makeTodo());
-        StatusSettings.addCustomStatus(coreStatuses, StatusConfiguration.makeInProgress());
-        StatusSettings.addCustomStatus(coreStatuses, StatusConfiguration.makeDone());
-        StatusSettings.addCustomStatus(coreStatuses, StatusConfiguration.makeCancelled());
+        StatusSettings.addCustomStatus(coreStatuses, Status.makeTodo().configuration);
+        StatusSettings.addCustomStatus(coreStatuses, Status.makeInProgress().configuration);
+        StatusSettings.addCustomStatus(coreStatuses, Status.makeDone().configuration);
+        StatusSettings.addCustomStatus(coreStatuses, Status.makeCancelled().configuration);
 
         /* -------------------- One row per status in the settings -------------------- */
         coreStatuses.customStatusTypes.forEach((status_type) => {
@@ -463,7 +464,7 @@ function createRowForTaskStatus(
 
     const taskStatusPreview = containerEl.createEl('pre');
     taskStatusPreview.addClass('row-for-status');
-    taskStatusPreview.textContent = statusType.previewText();
+    taskStatusPreview.textContent = new Status(statusType).previewText();
 
     const setting = new Setting(containerEl);
 
diff --git a/src/Status.ts b/src/Status.ts
index e5ce2402..35662678 100644
--- a/src/Status.ts
+++ b/src/Status.ts
@@ -21,7 +21,7 @@ export class Status {
      * @type {Status}
      * @memberof Status
      */
-    public static DONE: Status = new Status(StatusConfiguration.makeDone());
+    public static DONE: Status = Status.makeDone();
 
     /**
      * A default status of empty, used when things go wrong.
@@ -29,7 +29,7 @@ export class Status {
      * @static
      * @memberof Status
      */
-    public static EMPTY: Status = new Status(StatusConfiguration.makeEmpty());
+    public static EMPTY: Status = Status.makeEmpty();
 
     /**
      * The default Todo status. Goes to Done when toggled.
@@ -39,7 +39,7 @@ export class Status {
      * @type {Status}
      * @memberof Status
      */
-    public static TODO: Status = new Status(StatusConfiguration.makeTodo());
+    public static TODO: Status = Status.makeTodo();
 
     /**
      * The configuration stored in the data.json file.
@@ -100,6 +100,42 @@ export class Status {
         this.configuration = configuration;
     }
 
+    /**
+     * The default Done status. Goes to Todo when toggled.
+     */
+    static makeDone(): Status {
+        return new Status(new StatusConfiguration('x', 'Done', ' ', true));
+    }
+
+    /**
+     * A default status of empty, used when things go wrong.
+     */
+    static makeEmpty(): Status {
+        return new Status(new StatusConfiguration('', 'EMPTY', '', true));
+    }
+
+    /**
+     * The default Todo status. Goes to Done when toggled.
+     * User may later be able to override this to go to In Progress instead.
+     */
+    static makeTodo(): Status {
+        return new Status(new StatusConfiguration(' ', 'Todo', 'x', true));
+    }
+
+    /**
+     * The default Cancelled status. Goes to Todo when toggled.
+     */
+    static makeCancelled(): Status {
+        return new Status(new StatusConfiguration('-', 'Cancelled', ' ', true));
+    }
+
+    /**
+     * The default In Progress status. Goes to Done when toggled.
+     */
+    static makeInProgress(): Status {
+        return new Status(new StatusConfiguration('/', 'In Progress', 'x', true));
+    }
+
     /**
      * Create a Status representing the given, unknown indicator.
      *
@@ -121,4 +157,26 @@ export class Status {
     public isCompleted(): boolean {
         return this.indicator === 'x' || this.indicator === 'X';
     }
+
+    /**
+     * Return a one-line summary of the status, for presentation to users.
+     */
+    public previewText() {
+        let commandNotice = '';
+        if (Status.tasksPluginCanCreateCommandsForStatuses() && this.availableAsCommand) {
+            commandNotice = 'Available as a command.';
+        }
+        return `- [${this.indicator}] ${this.name}, next status is '${this.nextStatusIndicator}'. ${commandNotice}`;
+    }
+
+    /**
+     * Whether Tasks can yet create 'Toggle Status' commands for statuses
+     *
+     * This is not yet possible, and so some UI features are temporarily hidden.
+     * See https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1486
+     * Once that issue is addressed, this method can be removed.
+     */
+    public static tasksPluginCanCreateCommandsForStatuses(): boolean {
+        return false;
+    }
 }
diff --git a/src/StatusConfiguration.ts b/src/StatusConfiguration.ts
index 2a36ff63..0d807d8c 100644
--- a/src/StatusConfiguration.ts
+++ b/src/StatusConfiguration.ts
@@ -6,42 +6,6 @@
  * @class StatusConfiguration
  */
 export class StatusConfiguration {
-    /**
-     * The default Done status. Goes to Todo when toggled.
-     */
-    static makeDone(): StatusConfiguration {
-        return new StatusConfiguration('x', 'Done', ' ', true);
-    }
-
-    /**
-     * A default status of empty, used when things go wrong.
-     */
-    static makeEmpty(): StatusConfiguration {
-        return new StatusConfiguration('', 'EMPTY', '', true);
-    }
-
-    /**
-     * The default Todo status. Goes to Done when toggled.
-     * User may later be able to override this to go to In Progress instead.
-     */
-    static makeTodo(): StatusConfiguration {
-        return new StatusConfiguration(' ', 'Todo', 'x', true);
-    }
-
-    /**
-     * The default Cancelled status. Goes to Todo when toggled.
-     */
-    static makeCancelled(): StatusConfiguration {
-        return new StatusConfiguration('-', 'Cancelled', ' ', true);
-    }
-
-    /**
-     * The default In Progress status. Goes to Done when toggled.
-     */
-    static makeInProgress(): StatusConfiguration {
-        return new StatusConfiguration('/', 'In Progress', 'x', true);
-    }
-
     /**
      * The indicator used between the two square brackets in the markdown task.
      *
@@ -90,68 +54,4 @@ export class StatusConfiguration {
         this.nextStatusIndicator = nextStatusIndicator;
         this.availableAsCommand = availableAsCommand;
     }
-
-    /**
-     * Return a one-line summary of the status, for presentation to users.
-     */
-    public previewText() {
-        let commandNotice = '';
-        if (StatusConfiguration.tasksPluginCanCreateCommandsForStatuses() && this.availableAsCommand) {
-            commandNotice = 'Available as a command.';
-        }
-        return `- [${this.indicator}] ${this.name}, next status is '${this.nextStatusIndicator}'. ${commandNotice}`;
-    }
-
-    /**
-     * Whether Tasks can yet create 'Toggle Status' commands for statuses
-     *
-     * This is not yet possible, and so some UI features are temporarily hidden.
-     * See https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1486
-     * Once that issue is addressed, this method can be removed.
-     */
-    public static tasksPluginCanCreateCommandsForStatuses(): boolean {
-        return false;
-    }
-
-    /**
-     * 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 373c08e5..9e5144ae 100644
--- a/src/StatusRegistry.ts
+++ b/src/StatusRegistry.ts
@@ -1,5 +1,5 @@
 import { Status } from './Status';
-import { StatusConfiguration } from './StatusConfiguration';
+import type { StatusConfiguration } from './StatusConfiguration';
 
 /**
  * Tracks all the registered statuses a task can have.
@@ -195,11 +195,11 @@ export class StatusRegistry {
      */
     private addDefaultStatusTypes(): void {
         const defaultStatuses = [
-            new Status(StatusConfiguration.makeTodo()),
-            new Status(StatusConfiguration.makeInProgress()),
-            new Status(StatusConfiguration.makeDone()),
-            new Status(StatusConfiguration.makeCancelled()),
-            new Status(StatusConfiguration.makeEmpty()),
+            Status.makeTodo(),
+            Status.makeInProgress(),
+            Status.makeDone(),
+            Status.makeCancelled(),
+            Status.makeEmpty(),
         ];
 
         defaultStatuses.forEach((status) => {
diff --git a/src/StatusValidator.ts b/src/StatusValidator.ts
new file mode 100644
index 00000000..8dc65c46
--- /dev/null
+++ b/src/StatusValidator.ts
@@ -0,0 +1,45 @@
+import type { StatusConfiguration } from './StatusConfiguration';
+
+export class StatusValidator {
+    /**
+     * Determine whether the date in this object is valid, and return error message(s) for display if not.
+     */
+    public validate(statusConfiguration: StatusConfiguration): string[] {
+        const errors: string[] = [];
+
+        // Messages are added in the order fields are shown when editing statuses.
+        errors.push(...this.validateIndicator(statusConfiguration));
+        errors.push(...this.validateName(statusConfiguration));
+        errors.push(...this.validateNextIndicator(statusConfiguration));
+
+        return errors;
+    }
+
+    public validateIndicator(statusConfiguration: StatusConfiguration): string[] {
+        return StatusValidator.validateOneIndicator(statusConfiguration.indicator, 'Task Status Symbol');
+    }
+
+    public validateNextIndicator(statusConfiguration: StatusConfiguration): string[] {
+        return StatusValidator.validateOneIndicator(statusConfiguration.nextStatusIndicator, 'Task Next Status Symbol');
+    }
+
+    public validateName(statusConfiguration: StatusConfiguration) {
+        const errors: string[] = [];
+        if (statusConfiguration.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;
+    }
+}


Test patch:
diff --git a/tests/Status.test.ts b/tests/Status.test.ts
index 046bc347..89ffff2d 100644
--- a/tests/Status.test.ts
+++ b/tests/Status.test.ts
@@ -9,10 +9,23 @@ jest.mock('obsidian');
 window.moment = moment;
 
 describe('Status', () => {
-    describe('default configurations', () => {
-        expect(Status.DONE.configuration.previewText()).toEqual("- [x] Done, next status is ' '. ");
-        expect(Status.EMPTY.configuration.previewText()).toEqual("- [] EMPTY, next status is ''. ");
-        expect(Status.TODO.configuration.previewText()).toEqual("- [ ] Todo, next status is 'x'. ");
+    it('preview text', () => {
+        const configuration = new Status(new StatusConfiguration('P', 'Pro', 'Con', true));
+        expect(configuration.previewText()).toEqual("- [P] Pro, next status is 'Con'. ");
+    });
+
+    it('default configurations', () => {
+        expect(Status.DONE.previewText()).toEqual("- [x] Done, next status is ' '. ");
+        expect(Status.EMPTY.previewText()).toEqual("- [] EMPTY, next status is ''. ");
+        expect(Status.TODO.previewText()).toEqual("- [ ] Todo, next status is 'x'. ");
+    });
+
+    it('factory methods for default statuses', () => {
+        expect(Status.makeDone().previewText()).toEqual("- [x] Done, next status is ' '. ");
+        expect(Status.makeEmpty().previewText()).toEqual("- [] EMPTY, next status is ''. ");
+        expect(Status.makeTodo().previewText()).toEqual("- [ ] Todo, next status is 'x'. ");
+        expect(Status.makeCancelled().previewText()).toEqual("- [-] Cancelled, next status is ' '. ");
+        expect(Status.makeInProgress().previewText()).toEqual("- [/] In Progress, next status is 'x'. ");
     });
 
     it('should initialize with valid properties', () => {
diff --git a/tests/StatusRegistry.test.ts b/tests/StatusRegistry.test.ts
index c4963182..a0863288 100644
--- a/tests/StatusRegistry.test.ts
+++ b/tests/StatusRegistry.test.ts
@@ -33,16 +33,16 @@ describe('StatusRegistry', () => {
         // Assert
         expect(statusRegistry).not.toBeNull();
 
-        expect(doneStatus.indicator).toEqual(StatusConfiguration.makeDone().indicator);
+        expect(doneStatus.indicator).toEqual(Status.makeDone().indicator);
 
-        expect(statusRegistry.byIndicator('x').indicator).toEqual(StatusConfiguration.makeDone().indicator);
-        expect(statusRegistry.byIndicator('').indicator).toEqual(StatusConfiguration.makeEmpty().indicator);
-        expect(statusRegistry.byIndicator(' ').indicator).toEqual(StatusConfiguration.makeTodo().indicator);
-        expect(statusRegistry.byIndicator('-').indicator).toEqual(StatusConfiguration.makeCancelled().indicator);
-        expect(statusRegistry.byIndicator('/').indicator).toEqual(StatusConfiguration.makeInProgress().indicator);
+        expect(statusRegistry.byIndicator('x').indicator).toEqual(Status.makeDone().indicator);
+        expect(statusRegistry.byIndicator('').indicator).toEqual(Status.makeEmpty().indicator);
+        expect(statusRegistry.byIndicator(' ').indicator).toEqual(Status.makeTodo().indicator);
+        expect(statusRegistry.byIndicator('-').indicator).toEqual(Status.makeCancelled().indicator);
+        expect(statusRegistry.byIndicator('/').indicator).toEqual(Status.makeInProgress().indicator);
 
         // Detect unrecognised indicator:
-        expect(statusRegistry.byIndicator('?').indicator).toEqual(StatusConfiguration.makeEmpty().indicator);
+        expect(statusRegistry.byIndicator('?').indicator).toEqual(Status.makeEmpty().indicator);
     });
 
     it('should return empty status for lookup by unknown indicator with byIndicator()', () => {
@@ -136,17 +136,17 @@ describe('StatusRegistry', () => {
 
             // Assert
             expect(task).not.toBeNull();
-            expect(task!.status.indicator).toEqual(StatusConfiguration.makeTodo().indicator);
+            expect(task!.status.indicator).toEqual(Status.makeTodo().indicator);
 
             // In Tasks, TODO toggles to DONE, for consistency with earlier releases.
             // const toggledInProgress = task?.toggle()[0];
             // expect(toggledInProgress?.status.indicator).toEqual(Status.IN_PROGRESS.indicator);
 
             const toggledDone = task?.toggle()[0];
-            expect(toggledDone?.status.indicator).toEqual(StatusConfiguration.makeDone().indicator);
+            expect(toggledDone?.status.indicator).toEqual(Status.makeDone().indicator);
 
             const toggledTodo = toggledDone?.toggle()[0];
-            expect(toggledTodo?.status.indicator).toEqual(StatusConfiguration.makeTodo().indicator);
+            expect(toggledTodo?.status.indicator).toEqual(Status.makeTodo().indicator);
         });
 
         it('should allow task to toggle from cancelled to todo', () => {
@@ -171,10 +171,10 @@ describe('StatusRegistry', () => {
 
             // Assert
             expect(task).not.toBeNull();
-            expect(task!.status.indicator).toEqual(StatusConfiguration.makeCancelled().indicator);
+            expect(task!.status.indicator).toEqual(Status.makeCancelled().indicator);
 
             const toggledTodo = task?.toggle()[0];
-            expect(toggledTodo?.status.indicator).toEqual(StatusConfiguration.makeTodo().indicator);
+            expect(toggledTodo?.status.indicator).toEqual(Status.makeTodo().indicator);
         });
 
         it('should allow task to toggle through custom transitions', () => {
diff --git a/tests/StatusConfiguration.test.ts b/tests/StatusValidator.test.ts
similarity index 71%
rename from tests/StatusConfiguration.test.ts
rename to tests/StatusValidator.test.ts
index 22546faa..d13ba5fb 100644
--- a/tests/StatusConfiguration.test.ts
+++ b/tests/StatusValidator.test.ts
@@ -1,21 +1,11 @@
 import { StatusConfiguration } from '../src/StatusConfiguration';
+import { StatusValidator } from '../src/StatusValidator';
 
-describe('StatusConfiguration', () => {
-    describe('preview text', () => {
-        const configuration = new StatusConfiguration('P', 'Pro', 'Con', true);
-        expect(configuration.previewText()).toEqual("- [P] Pro, next status is 'Con'. ");
-    });
-
-    describe('factory methods for default statuses', () => {
-        expect(StatusConfiguration.makeDone().previewText()).toEqual("- [x] Done, next status is ' '. ");
-        expect(StatusConfiguration.makeEmpty().previewText()).toEqual("- [] EMPTY, next status is ''. ");
-        expect(StatusConfiguration.makeTodo().previewText()).toEqual("- [ ] Todo, next status is 'x'. ");
-        expect(StatusConfiguration.makeCancelled().previewText()).toEqual("- [-] Cancelled, next status is ' '. ");
-        expect(StatusConfiguration.makeInProgress().previewText()).toEqual("- [/] In Progress, next status is 'x'. ");
-    });
+describe('StatusValidator', () => {
+    const statusValidator = new StatusValidator();
 
     function checkValidation(statusConfiguration: StatusConfiguration, expectedMessages: string[]) {
-        const errors = statusConfiguration.validate();
+        const errors = statusValidator.validate(statusConfiguration);
         expect(errors).toEqual(expectedMessages);
     }
 
@@ -65,12 +55,12 @@ describe('StatusConfiguration', () => {
         describe('validate indicator', () => {
             it('valid indicator', () => {
                 const config = new StatusConfiguration('X', 'Completed', 'c', false);
-                expect(config.validateIndicator()).toStrictEqual([]);
+                expect(statusValidator.validateIndicator(config)).toStrictEqual([]);
             });
 
             it('invalid indicator', () => {
                 const config = new StatusConfiguration('XYZ', 'Completed', 'c', false);
-                expect(config.validateIndicator()).toStrictEqual([
+                expect(statusValidator.validateIndicator(config)).toStrictEqual([
                     'Task Status Symbol ("XYZ") must be a single character.',
                 ]);
             });
@@ -79,12 +69,12 @@ describe('StatusConfiguration', () => {
         describe('validate next indicator', () => {
             it('valid indicator', () => {
                 const config = new StatusConfiguration('c', 'Completed', 'X', false);
-                expect(config.validateNextIndicator()).toStrictEqual([]);
+                expect(statusValidator.validateNextIndicator(config)).toStrictEqual([]);
             });
 
             it('invalid next indicator', () => {
                 const config = new StatusConfiguration('c', 'Completed', 'XYZ', false);
-                expect(config.validateNextIndicator()).toStrictEqual([
+                expect(statusValidator.validateNextIndicator(config)).toStrictEqual([
                     'Task Next Status Symbol ("XYZ") must be a single character.',
                 ]);
             });