Repository: obsidian-tasks-group/obsidian-tasks
Pull request #2282 (2023-09-23 18:51:13)

refactor: Separate settings out of GlobalFilter
<!--- Provide a general summary of your changes in the Title above -->

# Description

Refactor `GlobalFilter` to not access or update `Settings`.

This is a step on the way to making `GlobalFilter` behave like `GlobalQuery`.

/CC @ilandikov for info.

## Motivation and Context

This is part 1 of #2281

- #2281

## How has this been tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

- Adding tests
- Running all existing tests
- Thorough exploratory testing in the demo vault.
- I will also work through the smoke tests before merging the PR.

## 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! -->

- [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://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://publish.obsidian.md/tasks-contributing) 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://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/Config/GlobalFilter.ts b/src/Config/GlobalFilter.ts
index 9158235a..e5c29332 100644
--- a/src/Config/GlobalFilter.ts
+++ b/src/Config/GlobalFilter.ts
@@ -1,20 +1,30 @@
 import * as RegExpTools from '../lib/RegExpTools';
-import { getSettings, updateSettings } from './Settings';
 
+/**
+ * GlobalFilter has its own data, independent of {@link Settings.globalFilter} value in {@link Settings}.
+ *
+ * See https://publish.obsidian.md/tasks/Getting+Started/Global+Filter
+ *
+ * Limitations:
+ * - All methods are static, so it is a collection of multiple static things
+ *     - This is in contrast to {@link GlobalQuery} what has just the one static method, {@link GlobalQuery.getInstance}.
+ *     - These static methods will be made non-static in a future change.
+ */
 export class GlobalFilter {
     static empty = '';
+    static _globalFilter = '';
+    static _removeGlobalFilter = false;
 
     static get(): string {
-        const { globalFilter } = getSettings();
-        return globalFilter;
+        return GlobalFilter._globalFilter;
     }
 
     static set(value: string) {
-        updateSettings({ globalFilter: value });
+        GlobalFilter._globalFilter = value;
     }
 
     static reset() {
-        updateSettings({ globalFilter: GlobalFilter.empty });
+        GlobalFilter.set(GlobalFilter.empty);
     }
 
     static isEmpty(): boolean {
@@ -35,7 +45,7 @@ export class GlobalFilter {
     }
 
     static removeAsWordFromDependingOnSettings(description: string): string {
-        const { removeGlobalFilter } = getSettings();
+        const removeGlobalFilter = GlobalFilter.getRemoveGlobalFilter();
         if (removeGlobalFilter) {
             return GlobalFilter.removeAsWordFrom(description);
         }
@@ -43,6 +53,20 @@ export class GlobalFilter {
         return description;
     }
 
+    /**
+     * @see setRemoveGlobalFilter
+     */
+    static getRemoveGlobalFilter() {
+        return GlobalFilter._removeGlobalFilter;
+    }
+
+    /**
+     * @see getRemoveGlobalFilter
+     */
+    static setRemoveGlobalFilter(removeGlobalFilter: boolean) {
+        GlobalFilter._removeGlobalFilter = removeGlobalFilter;
+    }
+
     /**
      * Search for the global filter for the purpose of removing it from the description, but do so only
      * if it is a separate word (preceding the beginning of line or a space and followed by the end of line
diff --git a/src/Config/SettingsTab.ts b/src/Config/SettingsTab.ts
index 6d4dc6f9..d12152bd 100644
--- a/src/Config/SettingsTab.ts
+++ b/src/Config/SettingsTab.ts
@@ -103,6 +103,7 @@ export class SettingsTab extends PluginSettingTab {
                 text.setPlaceholder('e.g. #task or TODO')
                     .setValue(GlobalFilter.get())
                     .onChange(async (value) => {
+                        updateSettings({ globalFilter: value });
                         GlobalFilter.set(value);
                         await this.plugin.saveSettings();
                     });
@@ -118,7 +119,7 @@ export class SettingsTab extends PluginSettingTab {
 
                 toggle.setValue(settings.removeGlobalFilter).onChange(async (value) => {
                     updateSettings({ removeGlobalFilter: value });
-
+                    GlobalFilter.setRemoveGlobalFilter(value);
                     await this.plugin.saveSettings();
                 });
             });
diff --git a/src/main.ts b/src/main.ts
index aee6a0dd..f5326f2e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -16,6 +16,7 @@ import { EditorSuggestor } from './Suggestor/EditorSuggestorPopup';
 import { StatusSettings } from './Config/StatusSettings';
 import type { Task } from './Task';
 import { tasksApiV1 } from './Api';
+import { GlobalFilter } from './Config/GlobalFilter';
 
 export default class TasksPlugin extends Plugin {
     private cache: Cache | undefined;
@@ -69,6 +70,8 @@ export default class TasksPlugin extends Plugin {
     async loadSettings() {
         const newSettings = await this.loadData();
         updateSettings(newSettings);
+        GlobalFilter.set(newSettings.globalFilter);
+        GlobalFilter.setRemoveGlobalFilter(newSettings.removeGlobalFilter);
         GlobalQuery.getInstance().set(newSettings.globalQuery);
         await this.loadTaskStatuses();
     }


Test patch:
diff --git a/tests/GlobalFilter.test.ts b/tests/GlobalFilter.test.ts
index 5b8838dd..35546b67 100644
--- a/tests/GlobalFilter.test.ts
+++ b/tests/GlobalFilter.test.ts
@@ -1,4 +1,4 @@
-import { resetSettings, updateSettings } from '../src/Config/Settings';
+import { resetSettings } from '../src/Config/Settings';
 import { GlobalFilter } from '../src/Config/GlobalFilter';
 
 describe('Global Filter tests', () => {
@@ -50,6 +50,16 @@ describe('Global Filter tests', () => {
         expect(GlobalFilter.includedIn('Important #task inside')).toEqual(true);
     });
 
+    it('Should check equality correctly', () => {
+        // Arrange
+        GlobalFilter.set('#task');
+
+        // Assert
+        expect(GlobalFilter.equals('#task')).toEqual(true);
+        expect(GlobalFilter.equals('#tasks')).toEqual(false);
+        expect(GlobalFilter.equals('#TODO')).toEqual(false);
+    });
+
     it('Should not match a string without Global Filter', () => {
         // Arrange
         GlobalFilter.set('testValue');
@@ -58,6 +68,14 @@ describe('Global Filter tests', () => {
         expect(GlobalFilter.includedIn('Without Global Filter')).toEqual(false);
     });
 
+    it('Should control whether to remove the global filter from displayed tasks', () => {
+        GlobalFilter.setRemoveGlobalFilter(false);
+        expect(GlobalFilter.getRemoveGlobalFilter()).toEqual(false);
+
+        GlobalFilter.setRemoveGlobalFilter(true);
+        expect(GlobalFilter.getRemoveGlobalFilter()).toEqual(true);
+    });
+
     it('Should remove Global Filter from the beginning of a string', () => {
         // Arrange
         const testValue = '#end';
@@ -102,7 +120,7 @@ describe('Global Filter tests with Remove Global Filter Setting', () => {
     it('Should remove Global Filter from a string when Setting is set to false', () => {
         // Arrange
         GlobalFilter.set('todo');
-        updateSettings({ removeGlobalFilter: false });
+        GlobalFilter.setRemoveGlobalFilter(false);
 
         // Assert
         expect(GlobalFilter.removeAsWordFromDependingOnSettings('This is absolutely todo')).toEqual(
@@ -113,7 +131,7 @@ describe('Global Filter tests with Remove Global Filter Setting', () => {
     it('Should remove Global Filter from a string when Setting is set to true', () => {
         // Arrange
         GlobalFilter.set('todo');
-        updateSettings({ removeGlobalFilter: true });
+        GlobalFilter.setRemoveGlobalFilter(true);
 
         // Assert
         expect(GlobalFilter.removeAsWordFromDependingOnSettings('This is absolutely todo')).toEqual(
@@ -134,7 +152,7 @@ describe('Global Filter tests with Remove Global Filter Setting', () => {
     ])(
         'should not remove Global Filter (%s) if it is in a substring for a task "- [ ] %s"',
         async (globalFilter: string, description: string) => {
-            updateSettings({ removeGlobalFilter: true });
+            GlobalFilter.setRemoveGlobalFilter(true);
             GlobalFilter.set(globalFilter);
 
             expect(GlobalFilter.removeAsWordFrom(description)).toEqual(description);
diff --git a/tests/Task.test.ts b/tests/Task.test.ts
index 45059eb6..348816c8 100644
--- a/tests/Task.test.ts
+++ b/tests/Task.test.ts
@@ -684,6 +684,7 @@ describe('to string', () => {
         const expectedLine = 'This is a task with #t as a global filter and also #t/some tags';
         expect(task.toString()).toStrictEqual(expectedLine);
         resetSettings();
+        GlobalFilter.reset();
     });
 });
 
@@ -1158,6 +1159,7 @@ describe('created dates on recurring task', () => {
     afterEach(() => {
         jest.useRealTimers();
         resetSettings();
+        GlobalFilter.reset();
     });
 
     it('should not set created date with disabled setting', () => {
@@ -1214,11 +1216,13 @@ describe('order of recurring tasks', () => {
         jest.useFakeTimers();
         jest.setSystemTime(new Date(2023, 5 - 1, 16));
         resetSettings();
+        GlobalFilter.reset();
     });
 
     afterAll(() => {
         jest.useRealTimers();
         resetSettings();
+        GlobalFilter.reset();
     });
 
     it('should put new task before old, by default', () => {
diff --git a/tests/TaskLineRenderer.test.ts b/tests/TaskLineRenderer.test.ts
index 4cf398f9..650d20e8 100644
--- a/tests/TaskLineRenderer.test.ts
+++ b/tests/TaskLineRenderer.test.ts
@@ -67,6 +67,8 @@ function getOtherLayoutComponents(parentElement: HTMLElement): string[] {
 describe('task line rendering', () => {
     afterEach(() => {
         resetSettings();
+        GlobalFilter.reset();
+        GlobalFilter.setRemoveGlobalFilter(false);
     });
 
     it('creates the correct span structure for a basic task', async () => {
@@ -118,7 +120,7 @@ describe('task line rendering', () => {
     };
 
     it('should render Global Filter when the Remove Global Filter is off', async () => {
-        updateSettings({ removeGlobalFilter: false });
+        GlobalFilter.setRemoveGlobalFilter(false);
         GlobalFilter.set('#global');
 
         const taskLine = '- [ ] This is a simple task with a #global filter';
@@ -128,7 +130,7 @@ describe('task line rendering', () => {
     });
 
     it('should not render Global Filter when the Remove Global Filter is on', async () => {
-        updateSettings({ removeGlobalFilter: true });
+        GlobalFilter.setRemoveGlobalFilter(true);
         GlobalFilter.set('#global');
 
         const taskLine = '- [ ] #global/subtag-shall-stay This is a simple task with a #global filter';