Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1569 (2023-01-22 20:14:34)

feat: Move '-' and '/' to custom statuses
<!--- Provide a general summary of your changes in the Title above -->

# Description

- Move '-' and '/' to custom statuses section
- Improve the wording on core and custom statuses sections
- The button for deleting custom statuses is now called reset instead, as it restores the two '-' and '/' buttons.

(This replaces #1568 which I closed because one of the commits had an incorrect message.)

## Motivation and Context

This makes a clear separation between the buttons that need custom CSS and the ones that don;'t.

## How has this been tested?

- Manually
- With unit tests

## Screenshots (if appropriate)

![image](https://user-images.githubusercontent.com/4840096/213930541-8276ae7a-de46-4fce-98fb-a6ca758861c8.png)

## 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] **New feature** (prefix: `feat` - non-breaking change which adds functionality)

## 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 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/SettingsTab.ts b/src/Config/SettingsTab.ts
index 1dba6369..ef297aad 100644
--- a/src/Config/SettingsTab.ts
+++ b/src/Config/SettingsTab.ts
@@ -473,13 +473,13 @@ export class SettingsTab extends PluginSettingTab {
         });
         addAllUnknownStatuses.infoEl.remove();
 
-        /* -------------------- 'Delete All Custom Status Types' button -------------------- */
+        /* -------------------- 'Reset Custom Status Types to Defaults' button -------------------- */
         const clearCustomStatuses = new Setting(containerEl).addButton((button) => {
             button
-                .setButtonText('Delete All Custom Status Types')
+                .setButtonText('Reset Custom Status Types to Defaults')
                 .setWarning()
                 .onClick(async () => {
-                    StatusSettings.deleteAllCustomStatuses(statusSettings);
+                    StatusSettings.resetAllCustomStatuses(statusSettings);
                     await updateAndSaveStatusSettings(statusSettings, settings);
                 });
         });
diff --git a/src/Config/StatusSettings.ts b/src/Config/StatusSettings.ts
index 7daefc1e..609002dd 100644
--- a/src/Config/StatusSettings.ts
+++ b/src/Config/StatusSettings.ts
@@ -13,12 +13,15 @@ import type { StatusCollection } from '../StatusCollection';
 export class StatusSettings {
     constructor() {
         this.coreStatusTypes = [
+            // The two statuses that do not need CSS styling
             Status.makeTodo().configuration,
-            Status.makeInProgress().configuration,
             Status.makeDone().configuration,
+        ]; // Do not modify directly: use the static mutation methods in this class.
+        this.customStatusTypes = [
+            // Any statuses that are always supported, but need custom CSS styling
+            Status.makeInProgress().configuration,
             Status.makeCancelled().configuration,
         ]; // Do not modify directly: use the static mutation methods in this class.
-        this.customStatusTypes = []; // Do not modify directly: use the static mutation methods in this class.
     }
     readonly coreStatusTypes: StatusConfiguration[]; // TODO - need to handle if this was not present in settings read from disk
     readonly customStatusTypes: StatusConfiguration[];
@@ -105,6 +108,19 @@ export class StatusSettings {
         statusSettings.customStatusTypes.splice(0);
     }
 
+    /**
+     * Restore the default custom statuses.
+     *
+     * @param statusSettings
+     */
+    public static resetAllCustomStatuses(statusSettings: StatusSettings) {
+        StatusSettings.deleteAllCustomStatuses(statusSettings);
+        const defaultSettings = new StatusSettings();
+        defaultSettings.customStatusTypes.forEach((s) => {
+            StatusSettings.addStatus(statusSettings.customStatusTypes, s);
+        });
+    }
+
     /**
      * Add a collection of custom supported statuses to a StatusSettings.
      * This can be used to quickly populate the user's settings.
diff --git a/src/Config/settingsConfiguration.json b/src/Config/settingsConfiguration.json
index 64815ed2..41b6bf9c 100644
--- a/src/Config/settingsConfiguration.json
+++ b/src/Config/settingsConfiguration.json
@@ -6,8 +6,8 @@
     "open": true,
     "notice": {
       "class": "setting-item-description",
-      "text": "These are the core status types that Tasks supports. You can add custom statuses in the section below.",
-      "html": null
+      "text": null,
+      "html": "<p>These are the core statuses that Tasks supports natively, with no need for custom CSS styling or theming.</p><p>You can add edit and add your own custom statuses in the section below.</p>"
     },
     "settings": [
       {
@@ -30,7 +30,7 @@
     "notice": {
       "class": "setting-item-description",
       "text": null,
-      "html": "<p>If you want support for additional status types outside of the core ones above, add them here with their status symbol.</p><p><b>Note</b>: Any statuses with the same symbol as any earlier statuses will be ignored.<br>You can confirm the actually loaded statuses by running the 'Create or edit task' command and looking at the Status drop-down.</p><p>See the <a href=\"https://obsidian-tasks-group.github.io/obsidian-tasks/getting-started/statuses/\">documentation</a>.</p>"
+      "html": "<p>You should first <b>select and install a CSS Snippet or Theme</b> to style custom checkboxes.</p><p>Then, use the buttons below to set up your custom statuses, to match your chosen CSS checkboxes.</p><p><b>Note</b> Any statuses with the same symbol as any earlier statuses will be ignored. You can confirm the actually loaded statuses by running the 'Create or edit task' command and looking at the Status drop-down.</p><p></p><p>See the <a href=\"https://obsidian-tasks-group.github.io/obsidian-tasks/getting-started/statuses/\">documentation</a> to get started!</p>"
     },
     "settings": [
       {


Test patch:
diff --git a/tests/Config/StatusSettings.test.StatusSettings_verify default status settings.approved.json b/tests/Config/StatusSettings.test.StatusSettings_verify default status settings.approved.json
index 81314bfc..9c800a96 100644
--- a/tests/Config/StatusSettings.test.StatusSettings_verify default status settings.approved.json
+++ b/tests/Config/StatusSettings.test.StatusSettings_verify default status settings.approved.json
@@ -7,19 +7,21 @@
       "availableAsCommand": true,
       "type": "TODO"
     },
-    {
-      "symbol": "/",
-      "name": "In Progress",
-      "nextStatusSymbol": "x",
-      "availableAsCommand": true,
-      "type": "IN_PROGRESS"
-    },
     {
       "symbol": "x",
       "name": "Done",
       "nextStatusSymbol": " ",
       "availableAsCommand": true,
       "type": "DONE"
+    }
+  ],
+  "customStatusTypes": [
+    {
+      "symbol": "/",
+      "name": "In Progress",
+      "nextStatusSymbol": "x",
+      "availableAsCommand": true,
+      "type": "IN_PROGRESS"
     },
     {
       "symbol": "-",
@@ -28,6 +30,5 @@
       "availableAsCommand": true,
       "type": "CANCELLED"
     }
-  ],
-  "customStatusTypes": []
+  ]
 }
diff --git a/tests/Config/StatusSettings.test.ts b/tests/Config/StatusSettings.test.ts
index c6db9494..372b436f 100644
--- a/tests/Config/StatusSettings.test.ts
+++ b/tests/Config/StatusSettings.test.ts
@@ -1,18 +1,29 @@
 import { verifyAsJson } from 'approvals/lib/Providers/Jest/JestApprovals';
 import { StatusSettings } from '../../src/Config/StatusSettings';
 import { Status } from '../../src/Status';
-import { StatusConfiguration } from '../../src/StatusConfiguration';
+import { StatusConfiguration, StatusType } from '../../src/StatusConfiguration';
 import { StatusRegistry } from '../../src/StatusRegistry';
 import type { StatusCollection } from '../../src/StatusCollection';
 
 describe('StatusSettings', () => {
     it('verify default status settings', () => {
         const defaultStatusSettings = new StatusSettings();
+        // Core statuses
+        expect(defaultStatusSettings.coreStatusTypes.length).toEqual(2);
+        expect(defaultStatusSettings.coreStatusTypes[0].symbol).toEqual(' ');
+        expect(defaultStatusSettings.coreStatusTypes[1].symbol).toEqual('x');
+
+        // Custom statuses
+        expect(defaultStatusSettings.customStatusTypes.length).toEqual(2);
+        expect(defaultStatusSettings.customStatusTypes[0].symbol).toEqual('/');
+        expect(defaultStatusSettings.customStatusTypes[1].symbol).toEqual('-');
+
         // This captures the default contents of both core and custom statuses
         verifyAsJson(defaultStatusSettings);
     });
 
-    function addThreeStatuses(settings: StatusSettings) {
+    function setThreeCustomStatuses(settings: StatusSettings) {
+        StatusSettings.deleteAllCustomStatuses(settings);
         const pro = new StatusConfiguration('P', 'Pro', 'C', false);
         const imp = new StatusConfiguration('!', 'Important', 'x', false);
         const con = new StatusConfiguration('C', 'Con', 'P', false);
@@ -25,22 +36,22 @@ describe('StatusSettings', () => {
     it('should add a status', () => {
         // Arrange
         const settings = new StatusSettings();
-        expect(settings.coreStatusTypes.length).toEqual(4);
-        expect(settings.customStatusTypes.length).toEqual(0);
+        expect(settings.coreStatusTypes.length).toEqual(2);
+        expect(settings.customStatusTypes.length).toEqual(2);
 
         // Act
         const newStatus = new StatusConfiguration('!', 'Important', 'x', false);
         StatusSettings.addStatus(settings.customStatusTypes, newStatus);
 
         // Assert
-        expect(settings.customStatusTypes.length).toEqual(1);
-        expect(settings.customStatusTypes[0]).toStrictEqual(newStatus);
+        expect(settings.customStatusTypes.length).toEqual(3);
+        expect(settings.customStatusTypes[2]).toStrictEqual(newStatus);
     });
 
     it('should replace a status, if present', () => {
         // Arrange
         const settings = new StatusSettings();
-        const { imp } = addThreeStatuses(settings);
+        const { imp } = setThreeCustomStatuses(settings);
         expect(settings.customStatusTypes.length).toEqual(3);
         expect(settings.customStatusTypes[1]).toStrictEqual(imp);
 
@@ -76,7 +87,7 @@ describe('StatusSettings', () => {
     it('should delete a status', () => {
         // Arrange
         const settings = new StatusSettings();
-        const { pro, imp, con } = addThreeStatuses(settings);
+        const { pro, imp, con } = setThreeCustomStatuses(settings);
         expect(settings.customStatusTypes.length).toEqual(3);
 
         // Act
@@ -96,7 +107,7 @@ describe('StatusSettings', () => {
     it('should delete all custom statuses', () => {
         // Arrange
         const settings = new StatusSettings();
-        addThreeStatuses(settings);
+        setThreeCustomStatuses(settings);
         expect(settings.customStatusTypes.length).toEqual(3);
 
         // Act
@@ -106,10 +117,53 @@ describe('StatusSettings', () => {
         expect(settings.customStatusTypes.length).toEqual(0);
     });
 
+    it('should reset all custom statuses', () => {
+        // Arrange
+        const settings = new StatusSettings();
+        // Stomp on the current custom settings.
+        settings.customStatusTypes.forEach((s) => {
+            StatusSettings.replaceStatus(
+                settings.customStatusTypes,
+                s,
+                new StatusConfiguration(s.symbol, 'NONSENSE NAME', 'x', false, StatusType.DONE),
+            );
+        });
+        // Add some additional custom settings.
+        StatusSettings.addStatus(
+            settings.customStatusTypes,
+            new StatusConfiguration('%', 'ANYTHING', '_', true, StatusType.NON_TASK),
+        );
+
+        // Act
+        StatusSettings.resetAllCustomStatuses(settings);
+
+        // Assert
+        expect(settings.customStatusTypes.length).toEqual(2);
+        expect(settings.customStatusTypes[0]).toMatchInlineSnapshot(`
+            StatusConfiguration {
+              "availableAsCommand": true,
+              "name": "In Progress",
+              "nextStatusSymbol": "x",
+              "symbol": "/",
+              "type": "IN_PROGRESS",
+            }
+        `);
+        expect(settings.customStatusTypes[1]).toMatchInlineSnapshot(`
+            StatusConfiguration {
+              "availableAsCommand": true,
+              "name": "Cancelled",
+              "nextStatusSymbol": " ",
+              "symbol": "-",
+              "type": "CANCELLED",
+            }
+        `);
+    });
+
     it('should apply settings to a StatusRegistry', () => {
         // Arrange
         const settings = new StatusSettings();
-        const { pro, imp, con } = addThreeStatuses(settings);
+        const { pro, imp, con } = setThreeCustomStatuses(settings);
+        expect(settings.coreStatusTypes.length).toEqual(2);
         expect(settings.customStatusTypes.length).toEqual(3);
 
         const statusRegistry = new StatusRegistry();
@@ -120,9 +174,9 @@ describe('StatusSettings', () => {
 
         // Assert
         const statuses = statusRegistry.registeredStatuses;
-        expect(statuses.length).toEqual(7);
-        expect(statuses[4]).toStrictEqual(new Status(pro));
-        expect(statuses[5]).toStrictEqual(new Status(imp));
-        expect(statuses[6]).toStrictEqual(new Status(con));
+        expect(statuses.length).toEqual(5);
+        expect(statuses[2]).toStrictEqual(new Status(pro));
+        expect(statuses[3]).toStrictEqual(new Status(imp));
+        expect(statuses[4]).toStrictEqual(new Status(con));
     });
 });